Esta pagina se ve mejor con JavaScript habilitado

SendMail, Dovecot, SpamAssassin, RainLoop, Sieve, OpenDkim, SPF, DMARC en FreeBSD

 ·  🎃 kr0m

En este artículo aprenderemos como montar un sistema de emails completo, podremos enviar/recibir emails, acceder mediante interfaz web, filtrar spam, definir filtros Sieve y firmar los emails salientes mediante DKIM.

Las tecnologías utilizadas para ello son:

  • SendMail : Envía y recibe emails
  • Dovecot : Permite el acceso a los emails almacenados desde la interfaz web
  • SpamAssassin : Filtra los emails entrantes en base a unas reglas determinadas
  • RainLoop : Interfaz web de acceso a los emails
  • Sieve : Sistema de filtrado/categorización de emails

Este artículo es bastante extenso así que lo he dividido en varias secciones:


SendMail

El primer paso será asegurarnos de que nuestro servidor conozca su propio hostname:

vi /etc/hosts

127.0.0.1               HellStorm HellStorm.alfaexploit.com mail.alfaexploit.com localhost localhost.my.domain

SendMail viene instalado por defecto en FreeBSD, arrancamos el servicio:

sysrc sendmail_enable=yes
sysrc sendmail_msp_queue_enable=yes
service sendmail start

Comprobamos que podamos acceder por red:

telnet X.X.X.X 25

220 HellStorm.alfaexploit.com ESMTP Sendmail 8.16.1/8.16.1; Sun, 1 Jan 2023 17:39:07 +0100 (CET)

En el magnífico handbook de FreeBSD podemos encontrar una guía muy útil de la cual he extraído la siguiente información.

Los permisos de acceso se filtran por origen en el fichero: /etc/mail/access

OK: Permitiremos la entrada del mail siempre y cuando el destino sea un dominio local(/etc/mail/local-host-names)
RELAY: Permitiremos el envío de mails a dominios de terceros a través de nuestro server
ERROR: Denegaremos el envío de mails con el mensaje indicado
SKIP: Denegaremos el envío de mails sin avisar al cliente que el email ha sido destruido
QUARANTINE: El mail se guardará en el servidor local pero no se enviará a su destino, el cliente recibirá una explicación de porque su email ha sido retenido

#From:cyberspammer.com ERROR:"550 We don't accept mail from spammers"
#From:okay.cyberspammer.com OK
#Connect:sendmail.org RELAY
#To:sendmail.org RELAY
#Connect:128.32 RELAY
#Connect:128.32.2 SKIP
#Connect:IPv6:1:2:3:4:5:6:7 RELAY
#Connect:suspicious.example.com QUARANTINE:Mail from suspicious host
#Connect:[127.0.0.3] OK
#Connect:[IPv6:1:2:3:4:5:6:7:8] OK

Pode defecto se aplica la política de OK desde cualquier ip siempre y cuando el destino sea local o esté listado en /etc/mail/local-host-names

Por ejemplo si intentamos enviar un email a kr0m@alfaexploit.com nos denegará el acceso pero para kr0m@localhost lo permitirá:

telnet 192.168.69.17 25

Trying 192.168.69.17...
Connected to 192.168.69.17.
Escape character is '^]'.
220 HellStorm.alfaexploit.com ESMTP Sendmail 8.16.1/8.16.1; Sun, 1 Jan 2023 17:39:07 +0100 (CET)
ehlo 192.168.69.17
250-HellStorm.alfaexploit.com Hello [192.168.69.17], pleased to meet you
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-8BITMIME
250-SIZE
250-DSN
250-ETRN
250-STARTTLS
250-DELIVERBY
250 HELP
mail from: test@kr0m.com
250 2.1.0 test@kr0m.com... Sender ok
rcpt to: kr0m@alfaexploit.com
250 2.1.5 kr0m@alfaexploit.com... Recipient ok
data
354 Enter mail, end with "." on a line by itself
prueba00
.
250 2.0.0 301Gd7Qr028498 Message accepted for delivery
quit
221 2.0.0 HellStorm.alfaexploit.com closing connection
Connection closed by foreign host.

En la sesión telnet no dará error pero en los logs podemos ver que no se ha enviado el mail:

tail -f /var/log/maillog

Jan  1 17:41:05 HellStorm sm-mta[32142]: 301Gd7Qr028498: to=kr0m@alfaexploit.com, delay=00:00:12, xdelay=00:00:00, mailer=esmtp, pri=30009, relay=alfaexploit.com., dsn=5.3.5, stat=Local configuration error

En cambio si el destinatario es kr0m@localhost veremos la entrega:

tail -f /var/log/maillog

Jan  1 17:45:31 HellStorm sm-mta[35699]: 301Gj39N027210: to=kr0m@localhost, delay=00:00:18, xdelay=00:00:00, mailer=local, pri=30396, relay=local, dsn=2.0.0, stat=Sent

El usuario kr0m puede leer este último email:

mail

Mail version 8.1 6/6/93.  Type ? for help.
"/var/mail/kr0m": 1 message 1 new
>N  1 test@kr0m.com         Sun Jan  1 17:45  13/455  
& 1
Message 1:
From test@kr0m.com Sun Jan  1 17:45:31 2023
Date: Sun, 1 Jan 2023 17:45:03 +0100 (CET)
From: test@kr0m.com
To: undisclosed-recipients:;

prueba01

& 

Añadimos alfaexploit.com al grupo de dominios locales:

vi /etc/mail/local-host-names

alfaexploit.com

Reiniciamos SendMail:

service sendmail restart

Volvemos a realizar la prueba:

telnet 192.168.69.17 25

Trying 192.168.69.17...
Connected to 192.168.69.17.
Escape character is '^]'.
220 HellStorm.alfaexploit.com ESMTP Sendmail 8.16.1/8.16.1; Sun, 1 Jan 2023 17:48:01 +0100 (CET)
ehlo 192.168.69.17
250-HellStorm.alfaexploit.com Hello [192.168.69.17], pleased to meet you
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-8BITMIME
250-SIZE
250-DSN
250-ETRN
250-STARTTLS
250-DELIVERBY
250 HELP
mail from: test@kr0m.com
250 2.1.0 test@kr0m.com... Sender ok
rcpt to: kr0m@alfaexploit.com
250 2.1.5 kr0m@alfaexploit.com... Recipient ok
data
354 Enter mail, end with "." on a line by itself
prueba02
.
250 2.0.0 301Gm17S090416 Message accepted for delivery
quit
221 2.0.0 HellStorm.alfaexploit.com closing connection
Connection closed by foreign host.

Podemos ver que ahora la entrega se ha realizado correctamente:

Jan  1 17:48:28 HellStorm sm-mta[13349]: 301Gm17S090416: to=kr0m@alfaexploit.com, delay=00:00:12, xdelay=00:00:00, mailer=local, pri=30402, relay=local, dsn=2.0.0, stat=Sent

Leemos el mail:

mail

Mail version 8.1 6/6/93.  Type ? for help.
"/var/mail/kr0m": 1 message 1 new
>N  1 test@kr0m.com         Sun Jan  1 17:48  13/461  
& 1
Message 1:
From test@kr0m.com Sun Jan  1 17:48:28 2023
Date: Sun, 1 Jan 2023 17:48:01 +0100 (CET)
From: test@kr0m.com
To: undisclosed-recipients:;

prueba02

& 

NOTA: Al meter el dominio alfaexploit.com en local-host-names es equivalente a haber creado las cuentas de email a partir de todos los usuarios que hay en el sistema operativo, para entornos mas grandes deberiamos buscar algún tipo de integración de usuarios virtuales a partir de una base de datos MySQL, LDAP o similar.

El fichero aliases define direcciones de correo que expanden a otros usuarios, direcciones externas, ficheros, programas u otros alias, en mi caso lo dejamos como viene por defecto pero root será un alias a kr0m@alfaexploit.com :

vi /etc/mail/aliases

MAILER-DAEMON: postmaster
postmaster: root
_dhcp: root
_pflogd: root
auditdistd: root
bin: root
bind: root
daemon: root
games: root
hast: root
kmem: root
mailnull: postmaster
man: root
news: root
nobody: root
operator: root
pop: root
proxy: root
smmsp: postmaster
sshd: root
system: root
toor: root
tty: root
usenet: news
uucp: root
abuse: root
security: root
ftp: root
ftp-bugs: ftp
root: kr0m@alfaexploit.com

Regeneramos el hash del fichero:

newaliases

Para realizar la conversión de una dirección de email a un mailbox utilizaremos el fichero virtusertable, el destino pueden ser mailboxes locales, mailboxes remotos, alias definidos en /etc/mail/aliases o ficheros. En mi caso no es necesario generar el fichero ya que no hago uso de dichas funcionalidades.

root@example.com root
postmaster@example.com postmaster@noc.example.net
@example.com joe

NOTA: Las entradas se comprueban en el orden en el que se encuentran en el fichero de configuración, en este ejemplo se ha configurado una entrada genérica para el dominio example.com, si el destinatario es cualquier distinto de root o postmaster se enviará a joe.

Si hemos generado el fichero /etc/mail/virtusertable, refrescamos el hash del fichero y reiniciamos SendMail:

makemap hash /etc/mail/virtusertable < /etc/mail/virtusertable
service sendmail restart

Si necesitamos que algún equipo externo pueda utilizar nuestro servidor para enviar emails debemos indicar las ips/dns en el fichero relay-domains, en mi caso no es necesario:

vi /etc/mail/relay-domains

service sendmail restart

Podemos encontrar la documentación sobre la configuración de SendMail en el fichero: /usr/share/sendmail/cf/README


Dovecot

Con SendMail podemos enviar y recibir emails pero no leer los recibidos, para ello necesitamos un servidor IMAP:

pkg install dovecot

Copiamos los ficheros de configuración de ejemplo:

cp -R /usr/local/etc/dovecot/example-config/* /usr/local/etc/dovecot/

Eliminamos la configuración SSL ya que accederemos a los emails por interfaz web, el único punto de contacto con el exterior será a través de SendMail para enviar/recibir emails.

vi /usr/local/etc/dovecot/conf.d/10-ssl.conf

ssl = no
#ssl_cert = </etc/ssl/certs/dovecot.pem
#ssl_key = </etc/ssl/private/dovecot.pem

La interfaz web solo accede a Dovecot vía IMAP:

vi /usr/local/etc/dovecot/dovecot.conf

protocols = imap lmtp

Bindeamos Dovecot a la ip del servidor:

vi /usr/local/etc/dovecot/dovecot.conf

listen = 192.168.69.17

Le indicamos a Dovecot donde debe organizar los emails Sent/Drafts/Spam/Trash/Archive y donde buscar los emails recibidos:

vi /usr/local/etc/dovecot/conf.d/10-mail.conf

mail_location = mbox:~/mboxDir:INBOX=/var/mail/%u
mail_privileged_group = mail

Ajustamos los permisos del directorio /var/mail:

chmod a+rwxt /var/mail

Creamos los directorios de email en el home del usuario:

su -l kr0m

mkdir mboxDir
chmod 700 mboxDir

touch mboxDir/Sent
touch mboxDir/Drafts
touch mboxDir/Spam
touch mboxDir/Trash
touch mboxDir/Archive

chmod 600 mboxDir/Sent
chmod 600 mboxDir/Drafts
chmod 600 mboxDir/Spam
chmod 600 mboxDir/Trash
chmod 600 mboxDir/Archive

exit

Arrancamos el servicio:

sysrc dovecot_enable=yes
service dovecot start


RainLoop

Instalamos RainLoop, una interfaz web que nos permitirá leer y enviar emails:

pkg install -y unzip curl wget socat
pkg install -y php82 php82-mbstring php82-tokenizer php82-pdo php82-pdo_mysql php82-phar php82-filter php82-zlib php82-dom php82-xml php82-xmlwriter php82-xmlreader php82-pecl-imagick php82-curl php82-session php82-ctype php82-iconv php82-gd php82-simplexml php82-zip php82-filter php82-tokenizer php82-calendar php82-fileinfo php82-intl php82-phar php82-soap php82-opcache php82-mysqli php82-bcmath php82-gmp

cp /usr/local/etc/php.ini-production /usr/local/etc/php.ini

Ajustamos algunos parámetros en el php.ini para poder enviar adjuntos de mayor tamaño:

vi /usr/local/etc/php.ini

date.timezone = Europe/Madrid
upload_max_filesize = 25M
post_max_size = 25M

Arrancamos el servicio php-fpm:

sysrc php_fpm_enable=yes
service php-fpm start

Instalamos un MySQL para poder almacenar los contactos del usuario:

pkg install -y mysql80-server

Arrancamos el servicio:

sysrc mysql_enable=yes
service mysql-server start

Configuramos la base de datos:

mysql_secure_installation

Creamos el fichero de configuración para poder entrar sin necesidad de escribir el password cada vez:

vi .my.cnf

[client]
user     = root
password = XXXXXXXXX

Aseguramos el acceso:

chmod 600 .my.cnf

Creamos la base de datos y un usuario con acceso a esta:

mysql

CREATE DATABASE rainloop;
CREATE USER rainloop@'192.168.69.17' IDENTIFIED WITH mysql_native_password BY 'XXXXXXXXX';
GRANT ALL PRIVILEGES ON rainloop.* TO rainloop@'192.168.69.17';
FLUSH PRIVILEGES;
exit;

Instalamos Nginx:

pkg install -y nginx

Arrancamos el servicio:

sysrc nginx_enable=yes
service nginx start

Creamos un vhost para RainLoop:

vim /usr/local/etc/nginx/rainloop.conf

server {

  listen 80;
  server_name mail.alfaexploit.com;

  root /usr/local/www/rainloop;

  index index.php;

  location / {
    try_files $uri $uri/ /index.php?$query_string;
  }

  location ^~ /data {
     deny all;
  }

  location ~ \.php$ {
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_index index.php;
    fastcgi_split_path_info ^(.+\.php)(.*)$;
    fastcgi_keep_conn on;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass 127.0.0.1:9000;
  }
  
}

Incluimos el vhost en la configuración general de Nginx, añadimos el include y aumentamos el máximo del tamaño del body en la sección http{}:

vi /usr/local/etc/nginx/nginx.conf

http {
    include       mime.types;
    default_type  application/octet-stream;
    client_max_body_size 25M;
    include rainloop.conf;

Reiniciamos el servicio:

service nginx reload

Nos bajamos e instalamos RainLoop:

mkdir -p /usr/local/www/rainloop
cd /usr/local/www/rainloop
wget http://www.rainloop.net/repository/webmail/rainloop-latest.zip
unzip rainloop-latest.zip -d /usr/local/www/rainloop
rm rainloop-latest.zip
chown -R www:www /usr/local/www/rainloop

Accedemos al panel de administración:
http://mail.alfaexploit.com/?admin

admin
12345

Lo primero será cambiar el password

Configuramos el acceso a la base de datos donde se guardarán los contactos del usuario:

Contacts -> MySQL:
mysql:host=192.168.69.17;port=3306;dbname=rainloop
rainloop
XXXXXXXXX

Le damos al botón Test para comprobar el correcto acceso.

Le indicamos al login que añada de forma automática el @alfaexploit.com al usuario:

Añadimos el dominio:

Domains -> alfaexploit.com
IMAP
X.X.X.X
143
SMTP
X.X.X.X
25
Use short login

NOTA: Use short login -> Ya que el @alfaexploit.com nos lo pondrá RainLoop de forma automática tal y como le hemos indicado en el paso anterior.

Accedemos a la cuenta de email como usuario:
http://mail.alfaexploit.com

kr0m
PASSWORD-SO

Configuramos los directorios donde guardar los emails:

Ruedecita dentada de abajo -> Folders
System Folders

Asignamos el directorio a cada Folder, Deleted items y Junk emails podemos darle al ojo para que no salgan.

Realizamos pruebas de envío y recepción de emails y consultamos los logs asociados:

tail -f /var/log/maillog


Sieve es un servicio de clasificación de emails, mediante filtros definidos por el sysadmin/usuario se pueden catalogar los emails entrantes, por ejemplo en base a ciertas cabeceras, origenes, esto resulta muy útil si combinamos SpamAssassin con Sieve ya que SpamAssassin nos marcará los emails con sus cabeceras y mediante Sieve filtraremos en base a estas.

SpamAssassin

Instalamos el milter de Spamassassin:

pkg install -y spamass-milter

Habilitamos el servicio spamd indicándole desde que ips aceptará conexiones, en este caso la propia ip del servidor:

sysrc spamd_enable="yes"
sysrc spamd_flags="-u spamd -H /var/spool/spamd -A 192.168.69.17"

Habilitamos el milter indicándole donde debe generar el socket Unix:

sysrc spamass_milter_enable="yes"
sysrc spamass_milter_socket="/var/run/spamass-milter.sock"

El resto de opciones que podríamos configurar los podemos ver en:

cat /usr/local/etc/rc.d/spamass-milter

: ${spamass_milter_enable="NO"}
: ${spamass_milter_socket="/var/run/spamass-milter.sock"}
: ${spamass_milter_flags="-f -p ${spamass_milter_socket} ${spamass_milter_localflags}"}
: ${spamass_milter_socket_owner="root"}
: ${spamass_milter_socket_group="wheel"}
: ${spamass_milter_socket_mode="644"}

Actualizamos las reglas de SpamAssassin y las compilamos:

sa-update
sa-compile

Arrancamos SpamAssassin y SpamAssassin-milter:

service sa-spamd start
service spamass-milter start

Generamos un fichero base de configuración para SendMail:

cd /etc/mail/
make

El comando anterior habrá generado un fichero con el nombre del host, definimos el milter al final del fichero:

vi HellStorm.mc

MAIL_FILTER(`spamassassin', `S=local:/var/run/spamass-milter.sock, F=, T=C:15m;S:4m;R:4m;E:10m')
define(`confINPUT_MAIL_FILTERS', `spamassassin')

Compilamos a M4 la nueva configuración:

make

Actualizamos la configuración de SendMail con la nuestra:

cp sendmail.cf sendmail.cf.ori
cp HellStorm.cf sendmail.cf

Reiniciamos el servicio:

service sendmail restart

Ponemos un tail para ver los logs, aparecerán algunos errores ya que SpamAssassin todavía no está configurado del todo:

tail -f /var/log/maillog

Nos enviamos un email y podemos ver en los logs como spamd intercepta el email entrante y le asigna una puntuación:

Jan  1 19:42:55 HellStorm spamd[41333]: spamd: result: . -5 - DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FROM,HTML_MESSAGE,RCVD_IN_DNSWL_HI,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_PASS,TVD_SPACE_RATIO scantime=0.3,size=2793,user=root,uid=58,required_score=5.0,rhost=192.168.69.17,raddr=192.168.69.17,rport=50983,mid=<CA+SWLLzfBAmUfQQTmiFejuKVkaRpkZKdd7ECv3qXLrPaW-QJAQ@mail.gmail.com>,autolearn=unavailable autolearn_force=no 

Si miramos el email en raw veremos las cabeceras añadidas por SpamAssassin:

X-Spam-Status: No, score=-5.2 required=5.0 tests=DKIM_SIGNED,DKIM_VALID,
	DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FROM,HTML_MESSAGE,
	RCVD_IN_DNSWL_HI,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_PASS,
	TVD_SPACE_RATIO autolearn=unavailable autolearn_force=no version=3.4.6
X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on
	HellStorm.alfaexploit.com

NOTA: Si queremos marcar como Spam un dominio o dirección en concreto añadimos al final del fichero local.cf de SpamAssassin:

vi /usr/local/etc/mail/spamassassin/local.cf
blacklist_from *@126.com
blacklist_from hacker@xxxxxxx.com

Reiniciamos el servicio:

service sa-spamd restart

Si vemos en los logs errores de este estilo:

Oct 21 22:21:19 DrWho spamd[3572]: plugin: eval failed: bayes: (in learn) locker: safe_lock: cannot create tmp lockfile /root/.spamassassin/bayes.lock.DrWho.alfaexploit.com.3572 for /root/.spamassassin/bayes.lock: Permission denied

Debemos definir la ubicación de la base de datos de SpamAssasin a una localización donde el usuario spamd tenga acceso:

vi /usr/local/etc/mail/spamassassin/local.cf

bayes_path /var/spamassassin/bayes_db/bayes
bayes_file_mode 0775

Creamos el directorio:

mkdir -p /var/spamassassin/bayes_db/

Le asignamos los permisos comentados en la documentación:

chmod 775 /var/spamassassin/bayes_db/
chown root:spamd /var/spamassassin/bayes_db/

Reiniciamos el servicio:

service sa-spamd restart

Ahora que ya marcamos nuestros emails habrá que catalogarlos según la reputación asignada por SpamAssassin, para ello emplearemos Sieve que nos permitirá configurar filtros en base a multitud de aspectos sobre el email, pero para poder utilizarlo debemos hacer que SendMail entregue los mails locales vía  LMTP a Dovecot:

cd /etc/mail/

vi HellStorm.mc

Sustitumos:

FEATURE(local_lmtp)

Por:

FEATURE(local_lmtp,`[IPC]',`FILE /var/run/dovecot/lmtp')dnl

Recompilamos la configuración:

make

Sustituimos la configuración actual por la nuestra:

cp HellStorm.cf sendmail.cf

Reiniciamos el servicio:

service sendmail restart

Instalamos el paquete necesario para que Dovecot soporte Sieve:

pkg install -y dovecot-pigeonhole

Lo habilitamos como protocolo:

vi /usr/local/etc/dovecot/dovecot.conf

protocols = imap lmtp sieve

Y como pulgin LMTP:

vi /usr/local/etc/dovecot/conf.d/20-lmtp.conf

protocol lmtp {
  mail_plugins = $mail_plugins sieve
}

En la configuración de plugins configuramos donde se almacenarán los filtros Sieve de los usuarios y que filtro se debe aplicar, además indicamos un filtro que siempre se ejecutará antes que los definidos por el usuario, ideal para que los sysadmins puedan realizar sus filtrados pre-user:

vi /usr/local/etc/dovecot/conf.d/90-plugin.conf

plugin {
    sieve = file:~/.sieve;active=~/.sieve/dovecot.sieve
    sieve_before = file:/var/lib/dovecot/default.sieve
}

Cuando se programan scripts en Sieve se debe indicar arriba del todo las librerias que requiere y luego ya hacer uso de ellas en el resto de script:

mkdir /var/lib/dovecot

vi /var/lib/dovecot/default.sieve
require ["fileinto"];
  
if header :contains "X-Spam-Flag" "YES" {
    fileinto "Spam";
    stop;
}

Compilamos el script:

sievec /var/lib/dovecot/default.sieve

Creamos el directorio de los scripts Sieve en el home del usuario:

su -l kr0m
mkdir /home/kr0m/.sieve
exit

Comprobamos que el módulo LMTP de Dovecot haya cargado el plugin Sieve:

doveconf -f service=lmtp mail_plugins

mail_plugins = sieve

Reiniciamos Dovecot:

service dovecot restart

Nos conectamos manualmente al Sieve-manager:

telnet 127.0.0.1 4190

Trying 127.0.0.1...
Connected to HellStorm.
Escape character is '^]'.
"IMPLEMENTATION" "Dovecot Pigeonhole"
"SIEVE" "fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext"
"NOTIFY" "mailto"
"SASL" "PLAIN"
"VERSION" "1.0"
OK "Dovecot ready."

Ya debería de ejecutarse el script sieve_before(/var/lib/dovecot/default.sieve) que mueve los emails marcados como spam por SpamAssassin al directorio Spam.

Accedemos al panel de administración de RainLoop y habilitamos el Sieve para nuestro dominio:
http://mail.alfaexploit.com/?admin

admin
XXXXXXX
Domains -> alfaexploit.com
Sieve configuration:
Allow sieve scripts
Allow custom user script
Server: 192.168.69.17 Port: 4190
Secure: None

Clickamos sobre el botón Update.

Accedemos con la cuenta de usuario normal y configuramos un filtro:

Configuración -> Filters

NOTA: Debemos filtrar el tráfico de red mediante firewall para evitar accesos no autorizados a los servicios de Sieve(4190)/SpamAssassin(783).


SPF

Mediante SPF indicaremos que servidores están autorizados a enviar emails de nuestro dominio, esto no es mas que unas entradas DNS indicando las ips, en mi caso quedaría del siguiente modo.

A 92.176.161.228 mail.alfaexploit.com
MX mail.alfaexploit.com
TXT spf2.0/mfrom,pra a mx -all
TXT v=spf1 mx -all

NOTA: La primera entrada es una entrada simple A resolviendo mail.alfaexploit.com a la ip, la segunda indica que servidor(MX) es el encargado de recibir emails, la tercera indica que la ip del servidor indicado en el registro MX del dominio está autorizado a enviar emails y la cuarte hace lo mismo que la tercera pero se trata de spf1.

SPF soporta dos tipos de fails:

  • hardfails: Cualquier email que provenga de una ip no permitida se eliminará, este comportamiento se indica al meter -all en la entrada DNS.
  • softfail: Cualquier email que provenga de una ip no permitida se marcará como Spam, este comportamiento se indica al meter ~all en la entrada DNS.

Comprobamos que todas las entradas resuelvan como es debido:

dig mx alfaexploit.com +short

0 mail.alfaexploit.com.
dig mail.alfaexploit.com +short
79.116.145.12
dig txt alfaexploit.com +short
"spf2.0/mfrom,pra a mx ~all"
"v=spf1 mx ~all"

DKIM

Mediante DKIM seremos capaces de firmar los emails salientes con una clave privada de este modo el receptor podrá verificar que el email fué enviado desde nuestro servidor y no desde otro haciéndose pasar por nosotros, la clave de este proceso consiste en publicar la pubkey en una entrada DNS para que el receptor pueda obtenerla y comprobar así la autenticidad del email.

Instalamos el milter OpenDkim:

pkg install -y opendkim

Habilitamos el servicio:

sysrc milteropendkim_enable=yes

Generamos la pareja private-key/pub-key:

mkdir /var/db/dkim
cd /var/db/dkim
opendkim-genkey -s smtp -d alfaexploit.com

NOTA: El parámetro -s smtp no es mas que el selector, se trata del un string con el que se tendrá que realizar la query DNS para obtener el valor de la pubkey.

La configuración de OpenDkim quedaría del siguiente modo:

vi /usr/local/etc/mail/opendkim.conf

Domain          alfaexploit.com
KeyFile         /var/db/dkim/smtp.private
InternalHosts   /var/db/dkim/internal_hosts
Selector        smtp
Socket          local:/var/run/milteropendkim/milter-opendkim.sock
Syslog          Yes

Ajustamos los permisos para que OpenDkim pueda generar el fichero de socket unix:

chown mailnull:mailnull /var/run/milteropendkim/

Definimos que ips podrán conectar al milter, en mi caso la ip del propio servidor:

vi /var/db/dkim/internal_hosts

192.168.69.17

Arrancamos el servicio:

service milter-opendkim start

Configuramos SendMail para que haga uso del milter nuevo:

cd /etc/mail

vi HellStorm.mc

MAIL_FILTER(`spamassassin', `S=local:/var/run/spamass-milter.sock, F=, T=C:15m;S:4m;R:4m;E:10m')
MAIL_FILTER(`dkim-filter', `S=/var/run/milteropendkim/milter-opendkim.sock, F=T, T=R:2m')
define(`confINPUT_MAIL_FILTERS', `spamassassin, dkim-filter')

Compilamos  y actualizamos la configuración:

make
cp HellStorm.cf sendmail.cf

Reiniciamos el servicio:

service sendmail restart

Si enviamos un email veremos que salen firmados.

DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=alfaexploit.com; s=smtp; t=1672616405; bh=G1jVXBHX92kUkSHkyQ6MzL5LYYAW8yEUeAJmHQPkbeQ=; h=Date:From:Subject:To; b=u49nGRAoDNY0tWxab5lbE7GSDvJ796snMaJbMDKA9+Iz5NSxeqGbfCCswsDo+yuZ8
	 96TXfWXO0tzo0kjSqCcrm5s2gdhNPeG9Q2+eTjs71pXJS7sonqw4AncamPQl2kOx3i
	 ZFCq67c/BK+gup601dSdrurFVLt57jpgUcOwD9Kc=

Pero debemos publicar nuestra pubkey en Internet para que los servidores que reciban los emails nuestros puedan comprobar que la firma del email se generó con la private key correspondiente a la pubkey publicada:

cat /var/db/dkim/smtp.txt

smtp._domainkey    IN    TXT    ( "v=DKIM1; k=rsa; "
      "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+6l6oAODR0hUzsHqb2StBHVKlXdemKhbRNaCNDdoqMH9yi7TOfYeO4Ko5Wnp4Gq449ur8h14Afvgji24DC6GCBNbHCcDh67M9HZW28BJPRoaaIInQHzt5+9oVa9BREliNa50gfbwmNS/WnrZ6o3X94xCCbb6xcdQJC6FCrGoyMQIDAQAB" )  ; ----- DKIM key smtp for alfaexploit.com

El registro TXT tendrá el siguiente contenido:

v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+6l6oAODR0hUzsHqb2StBHVKlXdemKhbRNaCNDdoqMH9yi7TOfYeO4Ko5Wnp4Gq449ur8h14Afvgji24DC6GCBNbHCcDh67M9HZW28BJPRoaaIInQHzt5+9oVa9BREliNa50gfbwmNS/WnrZ6o3X94xCCbb6xcdQJC6FCrGoyMQIDAQAB

La entrada DNS final debe contener el selector(smtp):

TXT smtp._domainkey v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+6l6oAODR0hUzsHqb2StBHVKlXdemKhbRNaCNDdoqMH9yi7TOfYeO4Ko5Wnp4Gq449ur8h14Afvgji24DC6GCBNbHCcDh67M9HZW28BJPRoaaIInQHzt5+9oVa9BREliNa50gfbwmNS/WnrZ6o3X94xCCbb6xcdQJC6FCrGoyMQIDAQAB

Si consultamos la entrada nos devuelve la pubkey configurada:

dig TXT smtp._domainkey.alfaexploit.com +short

"v=DKIM1;k=rsa;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+6l6oAODR0hUzsHqb2StBHVKlXdemKhbRNaCNDdoqMH9yi7TOfYeO4Ko5Wnp4Gq449ur8h14Afvgji24DC6GCBNbHCcDh67M9HZW28BJPRoaaIInQHzt5+9oVa9BREliNa50gfbwmNS/WnrZ6o3X94xCCbb6xcdQJC6FCrGoyMQIDAQAB;t=s;"

DMARC

DMARC son unos registros DNS donde indicamos a los servidores que reciben emails que deben hacer con este cuando el email entrante no supere el SPF/DKIM. Por supuesto cada ISP luego puede respetar lo indicado en el DMARC o no.

TXT _dmarc.alfaexploit.com
v=DMARC1\; p=reject\; rua=mailto:kr0m@alfaexploit.com\; ruf=mailto:kr0m@alfaexploit.com\; pct=100

Diseccionemos cada uno de los campos:

  • v: Versión del protocolo
  • p: Indica la política DMARC a seguir
  • rua: Donde se enviarán las notificaciones cuando se reciba un email que no supere el SPF/DKIM
  • ruf: Donde se enviará una copias de los emails-spam cuando se reciba un email que no supere el SPF/DKIM
  • pct: A que % de los emails recibidos se le debe aplicar el filtro DMARC

Las posibles políticas son:

  • none: Tratar el email sin aplicar DMARC
  • quarantine: Aceptar el email pero tratarlo como Spam
  • reject: Rechazar el email

Comprobamos que el registro DNS responda con la información correcta:

dig TXT _dmarc.alfaexploit.com +short

"v=DMARC1; p=reject; rua=mailto:kr0m@alfaexploit.com; ruf=mailto:kr0m@alfaexploit.com; pct=100"

Podemos ver como Google da por buenas las tres comprobaciones:


SSL RainLoop

Si vamos a acceder a la interfaz del RainLoop desde Internet mas vale hacerlo por SSL.

Para ello reconfiguraremos Nginx quitando el include del fichero rainloop.conf temporalmente:

vi /usr/local/etc/nginx/nginx.conf

...
http {
    include       mime.types;
    default_type  application/octet-stream;
    client_max_body_size 25M;
    #include rainloop.conf;
...

Reiniciamos el servicio:

service nginx restart

Instalamos ACME:

pkg install -y curl socat
curl https://get.acme.sh | sh -s email=kr0m@alfaexploit.com

Emitimos la petición de certificado:

/root/.acme.sh/acme.sh --issue -d mail.alfaexploit.com -w /usr/local/www/nginx --renew-hook 'service nginx restart'

[Mon Jan  2 08:02:05 CET 2023] Your cert is in: /root/.acme.sh/mail.alfaexploit.com/mail.alfaexploit.com.cer
[Mon Jan  2 08:02:05 CET 2023] Your cert key is in: /root/.acme.sh/mail.alfaexploit.com/mail.alfaexploit.com.key
[Mon Jan  2 08:02:05 CET 2023] The intermediate CA cert is in: /root/.acme.sh/mail.alfaexploit.com/ca.cer
[Mon Jan  2 08:02:05 CET 2023] And the full chain certs is there: /root/.acme.sh/mail.alfaexploit.com/fullchain.cer

Volvemos a incluir rainloop.conf

vi /usr/local/etc/nginx/nginx.conf

...
http {
    include       mime.types;
    default_type  application/octet-stream;
    client_max_body_size 25M;
    #include rainloop.conf;
...

Pero esta vez solo escuchará en el puerto 443, además mi tráfico pasa por un balanceadro de tráfico, así que utilizo proxy_protocol :

vi /usr/local/etc/nginx/rainloop.conf

server {

  listen 443 ssl proxy_protocol;
  server_name mail.alfaexploit.com;

  set_real_ip_from 192.168.69.11;
  real_ip_header proxy_protocol;

  root /usr/local/www/rainloop;

  ssl_certificate "/root/.acme.sh/mail.alfaexploit.com/fullchain.cer";
  ssl_certificate_key "/root/.acme.sh/mail.alfaexploit.com/mail.alfaexploit.com.key";

  index index.php;

  location / {
    try_files $uri $uri/ /index.php?$query_string;
  }

  location ^~ /data {
     deny all;
  }

  location ~ \.php$ {
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_index index.php;
    fastcgi_split_path_info ^(.+\.php)(.*)$;
    fastcgi_keep_conn on;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass 127.0.0.1:9000;
  }
  
}

Reiniciamos el servicio:

service nginx restart


SSL Sendmail

Por defecto SendMail viene con SSL habilitado con certificados autofirmados:

dnl Enable STARTTLS for receiving email.
define(`CERT_DIR', `/etc/mail/certs')dnl
define(`confSERVER_CERT', `CERT_DIR/host.cert')dnl
define(`confSERVER_KEY', `CERT_DIR/host.key')dnl
define(`confCLIENT_CERT', `CERT_DIR/host.cert')dnl
define(`confCLIENT_KEY', `CERT_DIR/host.key')dnl
define(`confCACERT', `CERT_DIR/cacert.pem')dnl
define(`confCACERT_PATH', `CERT_DIR')dnl
define(`confDH_PARAMETERS', `CERT_DIR/dh.param')dnl

Para configurar los nuestros primero haremos una copia de los originales:

cp -r /etc/mail/certs /etc/mail/certs.ori

Los certificados se obtienen en el servidor web, así que los sincronizamos mediante el siguiente script:

vi ~/.scripts/get_alfaexploit_certs.sh

!/usr/local/bin/bash

function sendTelegram {
        message=${@:1}
        #curl -s -X POST https://api.telegram.org/botAPI_KEY/sendMessage -d chat_id=CHAT_ID -d text="$message"
        curl -s -X POST https://api.telegram.org/bot535179217:AAGXRe1df_1WNgqxOCfC8VrCNKGqouhslLw/sendMessage -d chat_id=30418601 -d text="$message"
}

if [ -f "/etc/mail/certs/alfaexploit.com/fullchain.cer" ] && [ -f "/etc/mail/certs/alfaexploit.com/alfaexploit.com.key" ] && [ -f "/etc/mail/certs/alfaexploit.com/ca.cer" ]; then
    mv /etc/mail/certs/alfaexploit.com/fullchain.cer /etc/mail/certs/alfaexploit.com/fullchain.cer.ori
    mv /etc/mail/certs/alfaexploit.com/alfaexploit.com.key /etc/mail/certs/alfaexploit.com/alfaexploit.com.key.ori
    mv /etc/mail/certs/alfaexploit.com/ca.cer /etc/mail/certs/alfaexploit.com/ca.cer.ori
    PREV_CERTS_FOUND=1
else
    PREV_CERTS_FOUND=0
fi

if [ ! -d /etc/mail/certs/alfaexploit.com/ ]; then
    mkdir /etc/mail/certs/alfaexploit.com/
fi
fetch http://admin:XXXX@192.168.69.19:8080/fullchain.cer -o /etc/mail/certs/alfaexploit.com/fullchain.cer
if [ $? -ne 0 ]; then
    sendTelegram "$HOSTNAME-SMTP/SSL: Cant download http://admin:XXXX@192.168.69.19:8080/fullchain.cer"
fi
fetch http://admin:XXXX@192.168.69.19:8080/alfaexploit.com.key -o /etc/mail/certs/alfaexploit.com/alfaexploit.com.key
if [ $? -ne 0 ]; then
    sendTelegram "$HOSTNAME-SMTP/SSL: Cant download http://admin:XXXX@192.168.69.19:8080/alfaexploit.com.key"
fi
fetch http://admin:XXXX@192.168.69.19:8080/ca.cer -o /etc/mail/certs/alfaexploit.com/ca.cer
if [ $? -ne 0 ]; then
    sendTelegram "$HOSTNAME-SMTP/SSL: Cant download http://admin:XXXX@192.168.69.19:8080/ca.cer"
fi
chmod 600 /etc/mail/certs/alfaexploit.com/*

if [ $PREV_CERTS_FOUND -eq 1 ]; then
    for file in /etc/mail/certs/alfaexploit.com/*.ori; do
      md5_ori=$(md5 $file|awk '{print$4}')
      file=${file::-4}
      md5=$(md5 $file|awk '{print$4}')
      #echo "md5_ori: $md5_ori -- md5: $md5"
      if [ "$md5_ori" != "$md5" ]; then
        echo ">> New certs detected -> Restarting SendMail"
        service sendmail restart
        sendTelegram "$HOSTNAME:25-SSL alfaexploit certificates updated"
        break
      fi
    done
fi

check=$(echo Q|openssl s_client -connect 192.168.69.17:25 -starttls smtp 2>/dev/null | grep '0 s:' | grep 'CN =' | awk -F "CN = " '{print$2}' | awk -F "," '{print$1}')
#echo "check: $check"
if [ "$check" != "alfaexploit.com" ]; then
    sendTelegram "$HOSTNAME-SMTP/SSL Incorrect CommonName: $check"
fi

Asginamos los permisos necesarios:

chmod 700 ~/.scripts/get_alfaexploit_certs.sh

Lo crontabeamos:

crontab -e

*/30 * * * * /root/.scripts/get_alfaexploit_certs.sh >/dev/null 2>&1

Una vez copiados realizamos la siguiente configuración:

cd /etc/mail

vi HellStorm.mc

dnl Enable STARTTLS for receiving email.
define(`CERT_DIR', `/etc/mail/certs/alfaexploit.com')dnl
define(`confSERVER_CERT', `CERT_DIR/fullchain.cer')dnl
define(`confSERVER_KEY', `CERT_DIR/alfaexploit.com.key')dnl
define(`confCLIENT_CERT', `CERT_DIR/fullchain.cer')dnl
define(`confCLIENT_KEY', `CERT_DIR/alfaexploit.com.key')dnl
define(`confCACERT', `CERT_DIR/ca.cer')dnl
define(`confCACERT_PATH', `CERT_DIR')dnl

Compilamos la configuración:

make
cp HellStorm.cf sendmail.cf

Reiniciamos el servicio:

service sendmail restart

Dejamos un tail en el log para asegurarnos de que todo sigue funcionando:

tail -f /var/log/maillog

Realizamos una prueba manual para comprobar que nos sirve el certificado correcto:

echo Q|openssl s_client -connect mail.alfaexploit.com:25 -starttls smtp 2>/dev/null|grep ‘Verification:’

Verification: OK

Una forma de comprobar el SSL tanto la entrada como la salida es mediante esta web:
https://ssl-tools.net/mailservers
https://ssl-tools.net/mails


Reverse DNS

En cuanto a reverses DNS, los servidores de GMail/HotMail/OVH solo exigen que la ip de origen del mail tenga una reverse, da igual que reverse sea, NO exigen que corresponda con el dominio origen del email.


Mantenimiento cuentas

Con el tiempo los buzones pueden llenarse de emails por dejadez por parte de los usuarios provocando de este modo un consumo excesivo del espacio en disco, una denegación de servicio SMTP o incluso llegando a causar problemas al sistema operativo entero en caso de no haber particionado correctamente el disco o no haber aplicado cotas de disco.

Dovecot proporciona una herramienta de gestión del sistema de email llamada doveadm que además proporciona comandos útiles para la administración de cuentas de usuario.

Podemos ver donde almacena el usuario sus emails con el siguiente comando:

doveadm user kr0m

field	value
uid	1001
gid	1001
home	/home/kr0m
mail	mbox:~/mboxDir:INBOX=/var/mail/kr0m
system_groups_user	kr0m

Podemos consultar las carpetas de email:

doveadm mailbox list -u kr0m

Drafts
Trash
Archive
Sent
Spam
INBOX

Podemos ver de forma general el estado de cada carpeta:

doveadm mailbox status -u kr0m all Spam

Spam messages=4 recent=0 uidnext=2343 uidvalidity=1584649428 unseen=4 highestmodseq=4277 vsize=31212 guid=1e54ce3009d6735e13310000d09efc50 firstsaved=1661967302

También podemos obtener una lista de los email de dicha carpeta:

doveadm -f tab fetch -u kr0m "uid date.saved" mailbox Spam

uid	date.saved
2339	2022-08-31 19:35:02
2340	2022-09-01 07:14:41
2341	2022-09-01 09:25:54
2342	2022-09-01 22:00:09

Algo muy útil antes de borrar emails es hacer un búsqueda previa para asegurarnos de que vamos a borrar el contenido correcto:

doveadm search -u kr0m mailbox Spam savedbefore 1d

1e54ce3009d6735e13310000d09efc50 2339
1e54ce3009d6735e13310000d09efc50 2340

Borramos los email:

doveadm expunge -u kr0m mailbox Spam savedbefore 1d

Si consultamos ahora, veremos que solo quedan dos emails:

doveadm -f tab fetch -u kr0m "uid date.saved" mailbox Spam

uid	date.saved
2341	2022-09-01 09:25:54
2342	2022-09-01 22:00:09
doveadm mailbox status -u kr0m all Spam
Spam messages=2 recent=0 uidnext=2343 uidvalidity=1584649428 unseen=2 highestmodseq=4278 vsize=15667 guid=1e54ce3009d6735e13310000d09efc50 firstsaved=1662017154

Es buena idea crontabear un script que limpie cada cierto tiempo las cuentas:

vi ~/.scripts/clearMail

#!/usr/local/bin/bash
/usr/local/bin/doveadm expunge -u kr0m mailbox Spam savedbefore 1d
/usr/local/bin/doveadm expunge -u kr0m mailbox Trash savedbefore 7d

Asignamos los permisos necesarios al script:

chmod 700 ~/.scripts/clearMail

Crontabeamos el script:

crontab -e

00 00 * * * /root/.scripts/clearMail >/dev/null 2>&1

El comando expugne permite ejecutar los comandos en todos los usuarios existentes pero NO cuando se están utilizando usuarios del sistema:

If the -A option is present, the command will be performed for all users.
Using this option in combination with system users from userdb { driver = passwd } is not recommended, because it contains also users with a lower UID than the one configured with the first_valid_uid setting.
When the SQL userdb module is used make sure that the iterate_query setting in /etc/dovecot/dovecot-sql.conf.ext matches your database layout. When using the LDAP userdb module, make sure that the iterate_attrs and iterate_filter settings in /etc/dovecot/dovecot-ldap.conf.ext match your LDAP schema. Otherwise doveadm(1) will be unable to iterate over all users.

Debug

Podemos habilitar el debug de Dovecot y Sieve configurando ciertos parámetros:

vi conf.d/10-logging.conf

log_path = syslog
syslog_facility = mail
mail_debug = yes

Consultamos los logs:

tail -f /var/log/debug.log

Podemos dumpear toda la running-config de Dovecot con:

doveconf -a

Los plugins cargados en el servicio LMTP de Dovecot:

doveconf -f service=lmtp mail_plugins

Algunos enlaces interesantes:
https://wiki.dovecot.org/Pigeonhole/Sieve/Troubleshooting
https://wiki1.dovecot.org/Logging

Una web muy interesante cuando experimentamos problemas de entregabilidad es:
https://mxtoolbox.com/

Si te ha gustado el artículo puedes invitarme a un RedBull aquí