20/11/2021
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:
- commande
- numéro de version du protocol de filtre
- timestamp
- subsystem
- phase
- session ($6)
- token ($7)
- reverse dns
- adresse ip
Le filtre devra répondre comme suit:
filter-result|$6|$7|$resultat
où $resultat devra obligatoirement prendre une des valeurs suivantes:
- proceed
- junk
- rewrite|param
- reject|error
- disconnect|error
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
- 1: la commande à éxécuter
- 2: timestamp à passer à la commande
- 4-12: lecture des lignes de config et enregistrement du filtre. A partir de ce moment chaque ligne lue sur l'entrée standard devra faire l'objet d'une réponse sur la sortie standard
- 14: nouveau séparateur de champs
- 17: découpage de la ligne suivant le séparateur de champs
- 18-21: seules les lignes 'filter' sont à traiter (il n'y a pas de raison que notre script reçoive autre chose mais on ne sait jamais)
- 22: vérification du bon nombre de champs de la ligne (et réponse OK)
- 23: reverse dns valide, réponse OK
- 24: reverse dns invalide, réponse KO
- 25-30: éxécution de la commande
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
- la source
- premier résultat de man smtpd-filters
- un module Perl
- un script awk
Commentaires: https://github.com/bsdsx/blog_posts/issues/11
08/05/2021
202105080800 dovecot opensmtpd
Authentification des utilisateurs
Ce billet fait suite à la configuration initiale de mon serveur opensmtpd. Petit rappel des faits:
- un chroot minimal où opensmtpd est installé
- les utilisateurs valides ne sont pas dans /etc/passwd
- les courriels sont stockés à partir de /opt/vmail
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:
- ajouter du debug
- activer uniquement le pop3
- limiter les sockets en écoute
- écouter sur un port particulier
$ 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:
- lmtp + sieve + imaps
- relay + dkim
Mais pour se faire je ne saurais trop recommander la lecture de:
- https://poolp.org/posts/2019-12-23/mettre-en-place-un-serveur-de-mail-avec-opensmtpd-dovecot-et-rspamd
- https://rodolphe.breard.tf/en/article/how-to-deploy-a-personal-email-server/
Commentaires: https://github.com/bsdsx/blog_posts/issues/8
30/04/2021
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 suis dans un chroot donc le distingo base/paquet bofbof
- l'arborescence du chroot m'appartient
- je veux pouvoir modifier mes fichiers sans doas vim ou sudoedit
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:
- la taille des messages est limitée à 36700160 octets soit 35 Mo
- les options communément admises sont activées (8BITMIME, ENHANCEDSTATUSCODES et DSN)
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:
- les utilisateurs du chroot (seul root doit être valide)
- les adresses valides dans plusieurs déclinaisons (avec et sans domaine)
- un utilisateur externe à mon domaine
$ 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:
- abuse en postmaster
- postmaster en root
- root en foo
... 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:
- sans domaine
- avec domaine
- avec machine.domaine
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:
- filtre (ou qualifie) les connexions
- filtre (ou qualifie) le mail from
- accepte tout mon domaine
- délivre (et qualifie) des courriels
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