Tags

arm64 blacklistd bluetooth cu dns dovecot filter freebsd ipfw lets_encrypt opensmtpd shell ssl tls unbound

Powered by

blOg
maRkdown
awK
shEll

20/11/2021

[ opensmtpd filter ]

202111200800 opensmtpd filter

Les filtres avec OpenSMTPD

Pour protéger les ressources de mon serveur de courriel j'utilise ces filtres:

filter check_rdns   phase connect match !rdns   disconnect "550 no rDNS please fix"
filter check_fcrdns phase connect match !fcrdns disconnect "550 no FCrDNS please fix"

qui sont les pendants DNS des célèbres "pas d'bras, pas d'chocolat" et "T'as des baskets ? Bah ! Tu rentres pôôô" (chercher "sketch Maxime 'le videur'" pour la référence). Pour efficaces qu'ils soient, ces filtres ne peuvent pas grand chose face au côté répétitif des pénibles:

$ grep --count 'smtp connected address=193.56.29.192 host=<unknown>' /var/log/mail.log
109

Sachant qu'une seule connexion de ce genre génère 3 lignes de log:

Nov  2 00:01:33 backup_mx smtpd[60503]: 88166f7f21c951f4 smtp connected address=193.56.29.192 host=<unknown>
Nov  2 00:01:33 backup_mx smtpd[60503]: 88166f7f21c951f4 smtp failed-command command="" result="550 no rDNS please fix"
Nov  2 00:01:33 backup_mx smtpd[60503]: 88166f7f21c951f4 smtp disconnected reason=quit

Il est temps de bouter ces mécréants hors du royaume.

Table et mur de feu

J'ai déjà évoqué blacklistd qui, à mon sens, est la bonne réponse au problème mais OpenSMTPD ne le supporte pas (encore ?). Reste que le principe est le bon: placer l'adresse ip du rabouin dans une table (ipfw, npf, pf), un pool (ipf) ou un set (iptables/ipset/nftables/truc/je m'y perds dans ces linuxeries) et bloquer tout ce qui provient des adresses de cette table. Voyons comment faire avec un filtre.

Filtre

Un filtre OpenSMTPD se déclare de la façon suivante:

$ grep ^filter /etc/mail/smtpd.conf
filter nom_du_filtre proc-exec "/chemin/vers/mon/filtre arguments..."

Sans plus de précision ce filtre sera exécuté (froidement) en tant qu'utilisateur _smtpd du groupe _smtpd. Si les droits de cet utilisateur ne sont pas suffisants, on peut en spécifier un autre:

filter nom_du_filtre proc-exec "/chemin/vers/mon/filtre arguments..." user root group wheel

Un filtre s'active depuis la directive listen:

$ grep ^listen /etc/mail/smtpd.conf
listen on lo1 port 2525 filter nom_du_filtre

Un filtre de doit rien faire d'autre que lire depuis son entrée standard et écrire vers sa sortie standard (aka UNIX way of life). je ne vais pas paraphraser cet ancien billet mais en gros un bête script shell fait parfaitement l'affaire:

#!/bin/sh
set -eu
while true; do
    if read line; then
    ....
    fi
done

Le script devra commencer par lire les lignes suivantes:

config|smtpd-version|6.8.0p2
config|smtp-session-timeout|300
config|subsystem|smtp-in
config|admd|nom.de.la.machine
config|ready

Cette dernière ligne devra provoquer une réponse sur la sortie standard:

register|filter|smtp-in|connect
register|ready

Le filtre s'étant enregistré sur la phase connect du subsystem smtp-in, les prochaines lignes à lire auront cette forme:

filter|0.6|1636278882.100084|smtp-in|connect|b5c1acf410d9dbac|bdf7b27e9c960f99|<unknown>|192.168.192.90
filter|0.6|1636280533.458048|smtp-in|connect|3954af28cda90f23|f4a93480796395c4|foo.example.com|172.30.63.10

que l'on décompose en:

Le filtre devra répondre comme suit:

filter-result|$6|$7|$resultat

$resultat devra obligatoirement prendre une des valeurs suivantes:

Dehors les romanos

 1	FWCMD="${1:-true}"
 2	TIMESTAMP=${2:-1} # 0 no timestamp, 1 timestamp (default), 2 timestamp without nano seconds
 3	
 4	while true; do
 5	    if read line; then
 6	        case $line in
 7	            'config|ready') break;;
 8	        esac
 9	    fi
10	done
11	echo 'register|filter|smtp-in|connect'
12	echo 'register|ready'
13	
14	IFS='|'
15	while true; do
16	    if read line; then
17	        set -- $line
18	        case $1 in
19	            filter);;
20	            *) continue;;
21	        esac
22	        [ $# -ne 9 ] && echo "filter-result|$6|$7|proceed" && continue
23	        [ $8 != '<unknown>' ] && echo "filter-result|$6|$7|proceed" && continue
24	        echo "filter-result|$6|$7|disconnect|550 go away"
25	        ts=$3
26	        case $TIMESTAMP in
27	            0) ts='';;
28	            2) ts=${3%%.*};;
29	        esac
30	        ${FWCMD} $9 $ts || continue
31	    fi
32	done

ipfw car je le vaux bien

J'ai déjà parlé d'ipfw, de tables et de timestamp, ma commande sera la suivante:

/sbin/ipfw table t_pouilleux add

et le smtpd.conf devient:

filter nom_du_filtre proc-exec "/chemin/vers/mon/filtre '/sbin/ipfw table t_pouilleux add' 2" user root group wheel

jail ou chroot

Dans le cas où le filtre ne peut pas accéder directement à la commande du mur de feu, on peut imaginer un système à base de fifo:

...
fifo=/chemin/vers/fifo
...
echo "filter-result|$6|$7|disconnect|550 go away"
...
[ -p $fifo ] || continue
echo $9 $ts > $fifo || continue

et un autre script se chargera de la bonne commande:

$ cat fifo2fw.sh
#!/bin/sh

set -eu

echo $$ > $1

fifo=${2}

[ -p $fifo ] || mkfifo $fifo

while true; do
    if read ip timestamp < $fifo; then
        /sbin/ipfw table t_pouilleux add $ip $timestamp
    fi
done

Exemple d'une jail:

...
$fifo = "/chemin/complet/vers/fifo";
$pid  = "/var/run/fifo2fw.pid";
...
exec.prestart += "/chemin/vers/fifo2fw.sh $pid $fifo &";
exec.start += "/usr/local/sbin/smtpd -f /etc/mail/smtpd.conf";
exec.release += "pkill -9 -F $pid";
exec.release += "rm -f $fifo $pid";

La documentation

Commentaires: https://github.com/bsdsx/blog_posts/issues/11


Lien vers ce billet

08/05/2021

[ dovecot opensmtpd ]

202105080800 dovecot opensmtpd

Authentification des utilisateurs

Ce billet fait suite à la configuration initiale de mon serveur opensmtpd. Petit rappel des faits:

Pour authentifier un utilisateur, il me faut un fichier de type passwd:

user:hash du mot de passe:uid:gid:...

D'après la documentation d'opensmtpd il me faudrait un fichier avec ce format:

user  hash du mot de passe

D'après la documentation de dovecot (que je dois installer sinon mes utilisateurs vont avoir du mal à lire leurs courriels) il peut avoir cette forme:

user:{nom de la fonction de hachage}hash du mot de passe

Je vais vérifier qu'un fichier

user:hash du mot de passe

est bien compatible avec opensmtpd et dovecot en commençant par installer ce dernier.

Installation

En utilisant mon outil maison sjail:

$ sjail /zjails/mail pkg install dovecot
...

ou en mode barbare:

$ doas pkg -c /zjails/mail install --no-scripts --yes --no-repo-update dovecot
...
$ doas pw -R /zjails/mail groupadd dovecot  -g 143
$ doas pw -R /zjails/mail groupadd dovenull -g 144
$ doas pw -R /zjails/mail useradd  dovecot  -u 143 -g 143 -c "Dovecot User" -d /var/empty -s /usr/sbin/nologin
$ doas pw -R /zjails/mail useradd  dovenull -u 144 -g 144 -c "Dovecot login User" -d /var/empty -s /usr/sbin/nologin
$ cp /lib/libm.so.5 /zjails/mail/lib/
$ doas chroot /zjails/mail /sbin/ldconfig -elf /usr/local/lib /usr/local/lib/dovecot

Test initial de dovecot:

$ doas chroot /zjails/mail /usr/local/sbin/dovecot --version
Fatal: open(/dev/null) failed: No such file or directory

Rien de méchant mais il ne faudra pas oublier de démonter /zjails/mail/dev:

$ doas mount -t devfs devfs /zjails/mail/dev
$ doas devfs -m /zjails/mail/dev rule -s 4 applyset
$ doas chroot /zjails/mail /usr/local/sbin/dovecot --version
2.3.13 (89f716dc2)
$ doas umount /zjails/mail/dev

Je n'évoquerai plus les commandes concernant /dev. Pour obtenir la (longue) configuration par défaut:

$ doas chroot /zjails/mail /usr/local/sbin/dovecot -a -c /dev/null
...
[ plus de 1000 lignes ]

0 - Minimal

La configuration de dovecot se base sur plusieurs fichiers situés dans /usr/local/etc/dovecot/example-config/conf.d. Pour ma configuration de base je veux:

$ cat /zjail/mail/etc/mail/dovecot.conf
log_path = /dev/stderr
protocols = pop3
listen = ::1

service pop3-login {
  inet_listener pop3 {
    port = 1110
  }
}
$ doas chroot /zjails/mail /usr/local/sbin/dovecot -F -c /etc/mail/dovecot.conf
May 01 10:07:37 master: Info: Dovecot v2.3.13 (89f716dc2) starting up for imap

Et dans un autre shell:

$ netstat -an -f inet6 | grep 1110
tcp6       0      0 ::1.1110               *.*                    LISTEN

1 - Utilisateurs et mots de passe - dovecot

Pour obtenir le hash d'un mot de passe je peux utiliser smtpctl encrypt ou doveadm pw (ne pas oublier de garder une trace du mot de passe en clair avec |tee):

$ touch /zjails/mail/etc/mail/passwd
$ openssl rand -base64 12 | tee /zjails/mail/.foo | doas chroot /zjails/mail/ /usr/local/sbin/smtpctl encrypt | sed -e 's/^/foo:/' >> /zjails/mail/etc/mail/passwd
$ doas chroot /zjails/mail/ /usr/local/bin/doveadm -c /dev/null pw -s SHA512-CRYPT -p `openssl rand -base64 12 | tee /zjails/mail/.bar` | sed -e 's/^{SHA512\-CRYPT}/bar:/' >> /zjails/mail/etc/mail/passwd
$ cat /zjail/mail/etc/mail/dovecot.conf
log_path = /dev/stderr
protocols = pop3
listen = ::1

service pop3-login {
  inet_listener pop3 {
    port = 1110
  }
}

passdb {
  driver = passwd-file
  args = scheme=SHA512-CRYPT username_format=%n /etc/mail/passwd
}

userdb {
  driver = static
  args = uid=vmail gid=vmail home=/opt/vmail/%n
}

Premier essai:

$ telnet ::1 1110
Trying ::1...
Connected to localhost.
Escape character is '^]'.
+OK Dovecot ready.
user foo
+OK
pass KsFgKsTTyHdVu40W
+OK Logged in.
Connection closed by foreign host.

Côté serveur:

May 06 07:42:00 pop3-login: Info: Login: user=<foo>, method=PLAIN, rip=::1, lip=::1, mpid=2987, secured, session=<qkgqxaLBVHIAAAAAAAAAAAAAAAAAAAAB>
May 06 07:42:00 pop3(foo)<2987><qkgqxaLBVHIAAAAAAAAAAAAAAAAAAAAB>: Error: mail_location not set and autodetection failed: Mail storage autodetection failed with home=/opt/vmail/foo
May 06 07:42:00 pop3(foo)<2987><qkgqxaLBVHIAAAAAAAAAAAAAAAAAAAAB>: Info: mail_location not set and autodetection failed: Mail storage autodetection failed with home=/opt/vmail/foo top=0/0, retr=0/0, del=0/0, size=0

Je dois renseigner mail_location:

$ cat /zjail/mail/etc/mail/dovecot.conf
log_path = /dev/stderr
protocols = pop3
listen = ::1
mail_location = maildir:~/

service pop3-login {
  inet_listener pop3 {
    port = 1110
  }
}

passdb {
  driver = passwd-file
  args = scheme=SHA512-CRYPT username_format=%n /etc/mail/passwd
}

userdb {
  driver = static
  args = uid=vmail gid=vmail home=/opt/vmail/%n
}

Deuxième essai:

$ telnet ::1 1110
Trying ::1...
Connected to localhost.
Escape character is '^]'.
+OK Dovecot ready.
user foo
+OK
pass KsFgKsTTyHdVu40W
+OK Logged in.
quit
+OK Logging out.
Connection closed by foreign host.

2 - Utilisateurs et mots de passe - opensmtpd

Je modifie en conséquence la configuration d'opensmtpd pour tester l'authentification:

$ cat /zjails/mail/etc/mail/smtpd.conf
filter check_rdns   phase connect match !rdns   junk
filter check_fcrdns phase connect match !fcrdns junk
filter check_dns chain { check_rdns, check_fcrdns }

table trusted { 2001:DB8:666::/64 }
table myfroms { "@bsdsx\.fr$", "@.*\.bsdsx\.fr$" }
filter check_trusted phase connect match src <trusted> bypass
filter check_myfroms phase mail-from match mail-from regex <myfroms> disconnect "550 invalid mail from"
filter check_mail_from chain { check_trusted, check_myfroms }

filter filters chain { check_rdns, check_fcrdns, check_trusted, check_myfroms }
listen on ::1 port 2525 filter filters

table users file:/etc/mail/passwd
listen on ::1 port 5877 auth <users>

table domains { "bsdsx.fr", "*.bsdsx.fr" }
table aliases { abuse = postmaster, postmaster = root, root = foo }
action "local_mail" maildir "/opt/vmail/%{dest.user}" junk userbase <users> alias <aliases>
match from any for domain <domains> action "local_mail"

Mais le résultat est sans appel:

$ doas chroot /zjails/mail /usr/local/sbin/smtpd -f /etc/mail/smtpd.conf -d
smtpd: invalid listen option: auth requires tls/smtps

Un peu de pki:

$ openssl genrsa -out /zjails/mail/etc/mail/key.key 4096
$ openssl req -new -x509 -key /zjails/mail/etc/mail/key.key -subj "/CN=mail/C=FR/ST=IdF/L=Paris/O=Pouet inc/OU=Pouet" -out /zjails/mail/etc/mail/crt.crt -days 365
$ doas chown root /zjails/mail/etc/mail/key.key /zjails/mail/etc/mail/crt.crt

J'ajoute l'option -T lookup:

$ doas chroot /zjails/mail /usr/local/sbin/smtpd -f /etc/mail/smtpd.conf -d -T lookup

L'authentification se fait avec du base64:

$ printf 'foo' | openssl base64
Zm9v

Le truc à ne pas faire:

$ openssl base64 -in /zjails/mail/.foo
S3NGZ0tzVFR5SGRWdTQwVwo=

car on se retrouve avec un '\n' en trop:

$ cat /zjails/mail/.foo | tr -d '\n' | openssl base64
S3NGZ0tzVFR5SGRWdTQwVw==

Et à l'aide d'openssl s_client, le telnet du tls:

$ openssl s_client -6 -starttls smtp -connect localhost:5877
[ snip tout plein de trucs ]
---
read R BLOCK
ehlo localhost
...
auth login
334 VXNlcm5hbWU6
Zm9v
334 UGFzc3dvcmQ6
S3NGZ0tzVFR5SGRWdTQwVw==
235 2.0.0 Authentication succeeded
quit
221 2.0.0 Bye

Je teste mon utilisateur "bar":

$ printf 'bar' | openssl base64
YmFy
$ cat /zjails/mail/.bar | tr -d '\n' | openssl base64
aXpNNkRSenFydEw5M3g3Mw==
$ openssl s_client -6 -starttls smtp -connect localhost:5877
...
auth login
334 VXNlcm5hbWU6
YmFy
334 UGFzc3dvcmQ6
aXpNNkRSenFydEw5M3g3Mw==
235 2.0.0 Authentication succeeded
quit
221 2.0.0 Bye

Côté serveur:

f7a2e6aa567530c9 smtp connected address=[::1] host=localhost
f7a2e6aa567530c9 smtp tls ciphers=TLSv1.3:TLS_AES_256_GCM_SHA384:256
lookup: lookup "bar" as CREDENTIALS in table static:users -> "$6$.BEf4XECFaoMAlDu$x5RRN3/j58n7ke.LfJEwtF7hwWfiSqaxDM5s9Jc1B32UZiCiNfMKH9VueT6ey7wtO4YaQ/y.56PmJ.sHFokVr."
f7a2e6aa567530c9 smtp authentication user=bar result=ok
f7a2e6aa567530c9 smtp disconnected reason=quit

Pour finir

Ce fichier passwd termine la configuration initiale de mon serveur de courrier. Chaque étape de la réception du courriel a été testée et l'authentification des utilisateurs est fonctionnelle. Ce qu'il me reste à faire:

Mais pour se faire je ne saurais trop recommander la lecture de:

Commentaires: https://github.com/bsdsx/blog_posts/issues/8


Lien vers ce billet

30/04/2021

[ opensmtpd ]

202104302000 opensmtpd

Pour bien comprendre la configuration initiale de son serveur de courriel

Trouver une configuration pour opensmtpd prête à l'emploi n'est pas bien difficile mais j'aime bien partir de zéro et comprendre le pourquoi du comment. Pour se faire, je vais simplement installer opensmtpd dans un chroot, le lancer en mode debug et tester l'évolution de mon fichier de configuration.

Chroot et installation

J'utilise mon outil maison sjail pour initialiser mon chroot et installer opensmtpd:

$ doas zfs create -o quota=10G zcun/zjails/mail
$ doas chown -R dsx /zjails/mail
$ sjail /zjails/mail init
...
$ sjail /zjails/mail pkg install opensmtpd
...
$ sjail /zjails/mail pkg info
ca_root_nss-3.63               Root certificate bundle from the Mozilla Project
libasr-1.0.4                   Asynchronous DNS resolver library
libevent-2.1.12                API for executing callback functions on events or timeouts
opensmtpd-6.8.0,1              Security- and simplicity-focused SMTP server from OpenBSD

Un grand merci à bapt@ pour pkg et son option -c chroot. Pour vérifier le chroot:

$ doas chroot /zjails/mail /bin/sh
# echo *
bin dev etc lib libexec sbin tmp usr var
# ls
/bin/sh: ls: not found
# echo /bin/*
/bin/sh /bin/sleep
# echo sbin/*
sbin/ldconfig
# /usr/local/sbin/smtpd -h
version: OpenSMTPD 6.8.0p2
usage: smtpd [-dFhnv] [-D macro=value] [-f file] [-P system] [-T trace]
# exit
$ cat /zjails/mail/etc/passwd
root:*:0:1001:Privileged user:/var/empty:/bin/sh
nobody:*:65534:65534:Unprivileged user:/nonexistent:/usr/sbin/nologin
_smtpd:*:257:257:OpenSMTPD:/var/empty:/usr/sbin/nologin
_smtpq:*:258:258:OpenSMTPD queue user:/var/empty:/usr/sbin/nologin
vmail:*:2000:2000:OpenSMTPD virtual user:/var/empty:/usr/sbin/nologin
$ cat /zjails/mail/etc/group
wheel:*:1001:
nobody:*:65534:
_smtpd:*:257:
_smtpq:*:258:
vmail:*:2000:

Les binaires disponibles dans mon chroot étant réduits au strict minimum, le script de post-installation ne peut pas créer les utilisateurs/groupes. On peut s'inspirer de ce petit hook ou de la sortie (un peu pénible à lire) de doas pkg -c /zjails/mail info --raw opensmtpd. L'utilisateur/groupe vmail trouvera son utilité quand j'aborderai les utilisateurs.

L'emplacement théorique de la configuration se situe (depuis le chroot) dans /usr/local/etc/mail/ mais:

Je décide donc de placer ma configuration (depuis le chroot) dans le répertoire /etc/mail:

$ mkdir /zjails/mail/etc/mail

0/7 - Minimal

Le plus petit fichier de configuration d'opensmtpd tient en une seule ligne de quatre mots:

$ mkdir /zjails/mail/etc/mail
$ cat /zjails/mail/etc/mail/smtpd.conf
match from any reject
$ doas chroot /zjails/mail /usr/local/sbin/smtpd -f /etc/mail/smtpd.conf -n
configuration OK
$ doas chroot /zjails/mail /usr/local/sbin/smtpd -f /etc/mail/smtpd.conf -d
info: OpenSMTPD 6.8.0p2 starting

Cette configuration est vraiment sécurisée car seule la socket unix de contrôle est en écoute. Pour la beauté du geste, voici comment l'utiliser (depuis un autre shell):

$ ln /zjails/mail/usr/local/sbin/smtpctl /zjails/mail/tmp/sendmail
$ doas chgrp 258 /zjails/mail/tmp/sendmail
$ doas chroot /zjails/mail /tmp/sendmail -h
sendmail: illegal option -- h
usage: sendmail [-tv] [-f from] [-F name] to ...
$ date | doas chroot /zjails/mail /tmp/sendmail root
sendmail: command failed: 550 Invalid recipient: <root@cun.bsdsx.fr>

Et côté serveur:

bf23567dd3150185 smtp connected address=local host=cun.bsdsx.fr
bf23567dd3150185 smtp failed-command command="RCPT TO:<root@cun.bsdsx.fr> " result="550 Invalid recipient: <root@cun.bsdsx.fr>"
bf23567dd3150185 smtp disconnected reason=disconnect

1/7 - Listen local

La directive listen accepte de nombreux paramètres mais pour les tests le strict minimum fera l'affaire. Je prend soin de ne pas utiliser le port usuel afin de ne pas perturber l'hôte:

$ cat /zjails/mail/etc/mail/smtpd.conf
listen on ::1 port 2525

match from any reject
$ doas chroot /zjails/mail /usr/local/sbin/smtpd -f /etc/mail/smtpd.conf -d -v
...
debug: smtp: listen on [::1] port 2525 flags 0x400 pki "" ca ""
...

et dans un autre shell:

$ netstat -an -p tcp | grep 2525
tcp6       0      0 ::1.2525               *.*                    LISTEN

Première connexion:

C telnet ::1 2525
S Trying ::1...
S Connected to localhost.
S Escape character is '^]'.
S 220 cun.bsdsx.fr ESMTP OpenSMTPD
C EHLO localhost
S 250-cun.bsdsx.fr Hello localhost [::1], pleased to meet you
S 250-8BITMIME
S 250-ENHANCEDSTATUSCODES
S 250-SIZE 36700160
S 250-DSN
S 250 HELP
C MAIL FROM:<>
S 250 2.0.0 Ok
C RCPT TO:<root>
S 550 Invalid recipient: <root@cun.bsdsx.fr>
C quit
S 221 2.0.0 Bye
S Connection closed by foreign host.

On voit que par défaut:

2/7 - Filtrer les connexions

Mon serveur ne doit pas relayer le pourriel, il faut donc filtrer au plus tôt le bon grain de l'ivraie:

$ cat /zjails/mail/etc/mail/smtpd.conf
filter check_rdns   phase connect match !rdns   disconnect "550 no rDNS please fix"
filter check_fcrdns phase connect match !fcrdns disconnect "550 no FCrDNS please fix"
filter check_dns chain { check_rdns, check_fcrdns }

listen on ::1 port 2525 filter check_dns

match from any reject

Il est rare qu'une adresse lien-local possède un reverse dns:

$ telnet -s fe80::1%lo0 ::1 2525
Trying ::1...
Connected to localhost.
Escape character is '^]'.
550 no rDNS please fix
Connection closed by foreign host.

Côté serveur:

a2cc7671f3eb8824 smtp connected address=[fe80::1] host=<unknown>
a2cc7671f3eb8824 smtp failed-command command="" result="550 no rDNS please fix"
a2cc7671f3eb8824 smtp disconnected reason=quit

Pour tester le forward-confirmed reverse DNS, il me faut une adresse ip qui pointe vers un nom de machine qui pointe vers une autre adresse ip. Mon local-unbound vient à la rescousse:

$ cat ipv6doc.conf
server:
    local-zone: "ipv6.doc." refuse
        local-data: "un.ipv6.doc.          10800 IN AAAA 2001:db8:666::2"
        local-data: "deux.ipv6.doc.        10800 IN AAAA 2001:db8:666::1"
    local-zone: "0.0.0.0.6.6.6.0.8.b.d.0.1.0.0.2.ip6.arpa." refuse
        local-data-ptr: "2001:db8:666::1 un.ipv6.doc"
        local-data-ptr: "2001:db8:666::2 deux.ipv6.doc"
$ host un.ipv6.doc
un.ipv6.doc has IPv6 address 2001:db8:666::2
$ host 2001:db8:666::2
2.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.6.6.0.8.b.d.0.1.0.0.2.ip6.arpa domain name pointer deux.ipv6.doc.

J'ai bien un problème: "2001:db8:666::1" résoud vers "un.ipv6.doc" mais "un.ipv6.doc" a pour adresse "2001:db8:666::2".

$ doas ifconfig lo0 inet6 2001:db8:666::1 prefixlen 128 alias
$ telnet -s 2001:DB8:666::1 ::1 2525
Trying ::1...
Connected to localhost.
Escape character is '^]'.
550 no FCrDNS please fix
Connection closed by foreign host.
$ doas ifconfig lo0 inet6 2001:db8:666::1 -alias

Côté serveur:

5edc91252297e4fa smtp connected address=[2001:db8:666::1] host=un.ipv6.doc
5edc91252297e4fa smtp failed-command command="" result="550 no FCrDNS please fix"
5edc91252297e4fa smtp disconnected reason=quit

Côté serveur avec l'option -T filters:

e3bdf844cf6fb1a3 filters session-begin
e3bdf844cf6fb1a3 filters protocol phase=connect, resume=n, action=proceed, filter=check_rdns, query=[2001:db8:666::1]
e3bdf844cf6fb1a3 filters protocol phase=connect, resume=y, action=disconnect, filter=check_fcrdns, query=[2001:db8:666::1], response=550 no FCrDNS please fix

Les moins téméraires remplaceront disconnect par junk pour ne pas perdre de courriel, certains en parlent mieux que moi.

3/7 - Accepter du courriel pour mes utilisateurs

Je ne veux pas utiliser les entrées de /etc/passwd pour définir mes utilisateurs. Je verrai plus tard comment intégrer le tout avec dovecot et cette petite manipulation. Les uid et gid sont ceux de mon utilisateur vmail.

$ cat /zjails/mail/etc/mail/smtpd.conf
filter check_rdns   phase connect match !rdns   disconnect "550 no rDNS please fix"
filter check_fcrdns phase connect match !fcrdns disconnect "550 no FCrDNS please fix"
filter check_dns chain { check_rdns, check_fcrdns }

listen on ::1 port 2525 filter check_dns

table alias { abuse = postmaster, postmaster = root, root = foo }
table users { foo = "2000:2000:/var/empty", bar = "2000:2000:/var/empty" }
action "local_mail" maildir userbase <users> alias <alias>
match from any action "local_mail"

Les seuls adresses valides étant abuse, postmaster, root, foo et bar; je teste:

$ telnet ::1 2525
...
mail from:<>
250 2.0.0 Ok
rcpt to:<nobody>
550 Invalid recipient: <nobody@cun.bsdsx.fr>
rcpt to:<_smtpd>
550 Invalid recipient: <_smtpd@cun.bsdsx.fr>
rcpt to:<_smtpq>
550 Invalid recipient: <_smtpq@cun.bsdsx.fr>
rcpt to:<vmail>
550 Invalid recipient: <vmail@cun.bsdsx.fr>
rcpt to:<abuse>
250 2.1.5 Destination address valid: Recipient ok
rcpt to:<postmaster>
250 2.1.5 Destination address valid: Recipient ok
rcpt to:<root>
250 2.1.5 Destination address valid: Recipient ok
rcpt to:<foo>
250 2.1.5 Destination address valid: Recipient ok
rcpt to:<bar>
250 2.1.5 Destination address valid: Recipient ok
rcpt to:<bar@cun.bsdsx.fr>
250 2.1.5 Destination address valid: Recipient ok
rcp tto:<baz>
500 5.5.1 Invalid command: Command unrecognized
rcpt to:<foo@bsdsx.fr>
550 Invalid recipient: <foo@bsdsx.fr>
rcpt to:<foo@example.com>
550 Invalid recipient: <foo@example.com>
quit
221 2.0.0 Bye
Connection closed by foreign host.

Côté serveur avec l'option -T expand on pourra voir l'expansion de:

...
expand: lka_expand: address: abuse@cun.bsdsx.fr [depth=0]
...
expand: lka_expand: username: abuse [depth=1, sameuser=0]
...
expand: lka_expand: username: postmaster [depth=2, sameuser=0]
...
expand: lka_expand: username: root [depth=3, sameuser=0]
...
expand: lka_expand: username: foo [depth=4, sameuser=0]
expand: no .forward for user foo, just deliver

On remarque aussi que le domaine "bsdsx.fr" n'est pas valide: le seul domaine valide est celui de l'hôte soit @cun.bsdsx.fr. Ce domaine étant automatiquement ajouté aux adresses sans domaine foo est transformé en foo@cun.bsdsx.fr.

4/7 - Accepter du courriel pour mon domaine

Si j'ai un domaine, ce n'est pas pour avoir des adresses en @machine.bsdsx.fr:

$ cat /zjails/mail/etc/mail/smtpd.conf
filter check_rdns   phase connect match !rdns   disconnect "550 no rDNS please fix"
filter check_fcrdns phase connect match !fcrdns disconnect "550 no FCrDNS please fix"
filter check_dns chain { check_rdns, check_fcrdns }

listen on ::1 port 2525 filter check_dns

table alias { abuse = postmaster, postmaster = root, root = foo }
table users { foo = "2000:2000:/var/empty", bar = "2000:2000:/var/empty" }
action "local_mail" maildir userbase <users> alias <alias>
match from any for domain bsdsx.fr action "local_mail"

Je teste les déclinaisons:

où seule la version "avec domaine" doit être valide:

$ telnet ::1 2525
...
mail from:<>
250 2.0.0 Ok
rcpt to:<foo>
550 Invalid recipient: <foo@cun.bsdsx.fr>
rcpt to:<foo@bsdsx.fr>
250 2.1.5 Destination address valid: Recipient ok
rcpt to:<foo@cun.bsdsx.fr>
550 Invalid recipient: <foo@cun.bsdsx.fr>
quit
221 2.0.0 Bye
Connection closed by foreign host.

5/7 - Accepter du courriel des machines de mon domaine

Je veux centraliser les crontab des machines de mon domaine, je dois donc accepter tous les sous-domaines possibles:

$ cat /zjails/mail/etc/mail/smtpd.conf
filter check_rdns   phase connect match !rdns   disconnect "550 no rDNS please fix"
filter check_fcrdns phase connect match !fcrdns disconnect "550 no FCrDNS please fix"
filter check_dns chain { check_rdns, check_fcrdns }

listen on ::1 port 2525 filter check_dns

table domains { "bsdsx.fr", "*.bsdsx.fr" }
table aliases { abuse = postmaster, postmaster = root, root = foo }
table users { foo = "2000:2000:/var/empty", bar = "2000:2000:/var/empty" }
action "local_mail" maildir userbase <users> alias <aliases>
match from any for domain <domains> action "local_mail"

Je vérifie qu'il y a bien une comparaison stricte avec les domaines:

$ telnet ::1 2525
...
mail from:<>
250 2.0.0 Ok
rcpt to:<root@bsdsx.fr>
250 2.1.5 Destination address valid: Recipient ok
rcpt to:<root@foo.bsdsx.fr>
250 2.1.5 Destination address valid: Recipient ok
rcpt to: <root@absdsx.fr>
550 Invalid recipient: <root@absdsx.fr>
rcpt to:<root@bsdsxafr>
550 Invalid recipient: <root@bsdsxafr>
rcpt to:<root@bsdsx.fr.fr>
550 Invalid recipient: <root@bsdsx.fr.fr>
quit
221 2.0.0 Bye
Connection closed by foreign host.

Par rapport à la configuration précédente l'adresse foo étant automatiquement transformée en foo@cun.bsdsx.fr elle redevient une adresse valide de mon domaine.

6/7 - Refuser du courriel usurpant mon domaine

Il est temps d'être un peu plus strict concernant le mail from. Seules les machines de mon domaine peuvent utiliser un mail from en relation avec celui-ci. Pour me faciliter ce test je désactive la vérification dns. On pourrait croire que myfroms et domains font double emploi mais attention: domains ne doit pas contenir le caractère "@" et myfroms est une liste d'expressions régulières.

$ cat /zjails/mail/etc/mail/smtpd.conf
filter check_rdns   phase connect match !rdns   disconnect "550 no rDNS please fix"
filter check_fcrdns phase connect match !fcrdns disconnect "550 no FCrDNS please fix"
filter check_dns chain { check_rdns, check_fcrdns }

table trusted { 2001:DB8:666::/64 }
table myfroms { "@bsdsx\.fr$", "@.*\.bsdsx\.fr$" }
filter check_trusted phase mail-from match src <trusted> bypass
filter check_myfroms phase mail-from match mail-from regex <myfroms> disconnect "550 invalid mail from"
filter check_mail_from chain { check_trusted, check_myfroms }

listen on ::1 port 2525 filter check_mail_from

table domains { "bsdsx.fr", "*.bsdsx.fr" }
table aliases { abuse = postmaster, postmaster = root, root = foo }
table users { foo = "2000:2000:/var/empty", bar = "2000:2000:/var/empty" }
action "local_mail" maildir userbase <users> alias <aliases>
match from any for domain <domains> action "local_mail"

Ce filtre est une déclinaison personnelle de celui fourni par la documentation:

match !from src <other-relays> mail-from "@example.com" for any reject

Son principal avantage est qu'il coupe la connexion dès la phase mail from alors qu'un match va qualifier tous les destinataires comme étant invalides.

Une connexion hors domaine:

$ telnet ::1 2525
...
mail from:<root@bsdsx.fr>
550 invalid mail from
Connection closed by foreign host.

$ telnet ::1 2525
...
mail from:<root@cun.bsdsx.fr>
550 invalid mail from
Connection closed by foreign host.

$ telnet ::1 2525
...
mail from:<root@example.com>
250 2.0.0 Ok
quit

Une connexion du domaine:

$ doas ifconfig lo0 inet6 2001:db8:666::1 prefixlen 128 alias
$ telnet -s 2001:DB8:666::1 ::1 2525
...
mail from:<root@bsdsx.fr>
250 2.0.0 Ok
quit
$ telnet -s 2001:DB8:666::1 ::1 2525
...
mail from:<root@cun.bsdsx.fr>
250 2.0.0 Ok
quit
$ doas ifconfig lo0 inet6 2001:db8:666::1 -alias

Il serait tentant d'exiger d'un mail-from qu'il corresponde à une adresse mais il semblerait que cela soit une FBI (Fausse Bonne Idée).

7/7 - Délivrer le courriel de mon domaine

Le $HOME de mes utilisateurs étant invalide, les courriels seront stockés dans /opt/vmail. J'en profite pour réactiver le filtre check_dns en mode junk:

$ mkdir -p /zjails/mail/opt/vmail && doas chown -R 2000 /zjails/mail/opt/vmail
$ cat /zjails/mail/etc/mail/smtpd.conf
filter check_rdns   phase connect match !rdns   junk
filter check_fcrdns phase connect match !fcrdns junk
filter check_dns chain { check_rdns, check_fcrdns }

table trusted { 2001:DB8:666::/64 }
table myfroms { "@bsdsx\.fr$", "@.*\.bsdsx\.fr$" }
filter check_trusted phase mail-from match src <trusted> bypass
filter check_myfroms phase mail-from match mail-from regex <myfroms> disconnect "550 invalid mail from"
filter check_mail_from chain { check_trusted, check_myfroms }

filter filters chain { check_rdns, check_fcrdns, check_trusted, check_myfroms }
listen on ::1 port 2525 filter filters

table domains { "bsdsx.fr", "*.bsdsx.fr" }
table aliases { abuse = postmaster, postmaster = root, root = foo }
table users { foo = "2000:2000:/var/empty", bar = "2000:2000:/var/empty" }
action "local_mail" maildir "/opt/vmail%{rcpt}" junk userbase <users> alias <aliases>
match from any for domain <domains> action "local_mail"

Mon premier pourriel (l'adresse lien-local n'ayant pas de reverse dns):

$ telnet -s fe80::1%lo0 ::1 2525
...
mail from:<>
250 2.0.0 Ok
rcpt to:<foo>
250 2.1.5 Destination address valid: Recipient ok
data
354 Enter mail, end with "." on a line by itself
Subject: deliver test but junk

It works !
Check header for junk.

@+
.
250 2.0.0 09e5c671 Message accepted for delivery
quit
221 2.0.0 Bye
Connection closed by foreign host.

Côté serveur:

580c6c127534bb9f smtp connected address=[fe80::1] host=<unknown>
580c6c127534bb9f smtp message msgid=09e5c671 size=499 nrcpt=1 proto=ESMTP
580c6c127534bb9f smtp envelope evpid=09e5c6717c6aa838 from=<> to=<foo@cun.bsdsx.fr>
580c6c14a21d7761 mda delivery evpid=09e5c6717c6aa838 from=<> to=<foo@cun.bsdsx.fr> rcpt=<foo@cun.bsdsx.fr> user=foo delay=54s result=Ok stat=Delivered
580c6c127534bb9f smtp disconnected reason=quit

L'arborescence générée:

$ doas tree -a /zjails/mail/opt/vmail/foo
/zjails/mail/opt/vmail/foo
|-- .Junk
|   |-- cur
|   |-- new
|   |   `-- 1619801197.b213c4db.cun.bsdsx.fr
|   `-- tmp
|-- cur
|-- new
`-- tmp

7 directories, 1 file

Et le fameux pourriel:

$ doas cat /zjails/mail/opt/vmail/foo/.Junk/new/1619801197.b213c4db.cun.bsdsx.fr
Delivered-To: foo@cun.bsdsx.fr
X-Spam: Yes
Received: from cun (<unknown> [fe80::1])
        by cun.bsdsx.fr (OpenSMTPD) with ESMTP id 09e5c671
        for <foo@cun.bsdsx.fr>;
        Fri, 30 Apr 2021 18:45:46 +0200 (CEST)
Subject: deliver test but junk
Date: Fri, 30 Apr 2021 18:45:46 +0200 (CEST)
Message-ID: <580c6c13692a128a@cun.bsdsx.fr>

It works !
Check header for junk.

@+

Pour finir

Ma configuration initiale:

La prochaine étape abordera l'authentificaton des utilisateurs afin qu'ils puissent lire et envoyer du courriel.

Commentaires: https://github.com/bsdsx/blog_posts/issues/7


Lien vers ce billet