‌ ‌


Cet article n’est pas un copy pasta et ça marche, il faut prendre son temps pour comprendre comment chaque outil fonctionne :)


Le mail, c’est un enfer sur plein de points et beaucoup préfèrent en laisser la gestion à de gros organismes afin de ne pas s’embêter avec…

Problème, ces grands organismes, vous saurez jamais vraiment ce qu’ils font avec vos mails… Suffit qu’il y’ait du contenu sensible ou encore plus drôle, du contenu mal interprété et on voit vite le bordel (coucou GMail…).

Du coup quelles solutions pour éviter de confier ses mails à un tiers de confiance ?‌‌ Les héberger sois même sur son propre serveur !

Mais bon comme dit c’est un sacré bazar, mais c’est aussi pour ça que je rédige ce post.

Pourquoi OpenSMTPD au lieu de PostFix ?

Je sais pas vous, mais vous avez essayé de regarder la documentation de PostFix ?‌‌ Elle est juste immonde et désolé mais bosser avec des outils avec une doc en carton, sans façon.

OpenSMTPD est un serveur SMTP conçu pour être fiable, simple, rapide, et surtout sécurisé (les grands commandements des projets OpenBSD entre autres).

Pour l’instant la team OpenBSD stipule que ce n’est pas encore vraiment prêt à tenir la charge d’énorme domaines niveau trafic, cependant pour des domaines de petites et de moyennes taille, c’est juste parfait !

Ok mais c’est quoi tout le reste ?

Un serveur SMTP suffit pour envoyer et recevoir des mails, cependant il y a pleins d’autres aspects qu’il faut prendre en compte.

rspamd : Servira à signer avec DKIM vos mails et aussi filtrer ce qui rentre avec de l’antispam etc…‌‌Dovecot : Pouvoir lire ses mails c’est pratique et il propose IMAP comme protocole.‌‌PostgreSQL : PAM c’est rigolo 2 secondes mais pour de l’usage multi-domaines, c’est pas super génial, donc stocker tout ce qui est identifiants, etc… une BDD c’est le mieux. (OpenSMTPD et Dovecot supportent également MySQL et SQLite 3)

Pré-requis

Pour commencer, ça paraît évident, mais il vous faut un domaine, prenez ce qui vous conviens chez Gandi ou OVH (ou n’importe quel autre registar).‌‌

Verifiez que votre registar / l’organisme qui héberge votre zone dns supporte DNSSEC, c’est très important pour éviter le dns-hijack.

Ensuite le mail étant un sacré bordel, je vous recommande très fortement de dédier une machine virtuelle ou alors une machine complète pour votre serveur mail.‌‌

Il est important que celle-ci puisse avoir sa propre IPv4/v6 publique et que vous puissiez changer les reverses dns.

Vous pouvez le faire de manière sûre et certaine chez :

  • OVH / OVH Télécom
  • Scaleway (dédiés)
  • Digital Ocean (Blocage du port 25)
  • Vultr (après demande au support pour déblocage du port 25)
  • Hetzner

Pour toutes les étapes qui suivront, on part du principe que vous respectez tous les pré-requis ci-dessus et que vous avez une machine/vm/whatever sous OpenBSD fonctionnelle (nous sommes actuellement à la version 6.9 du système).

PostgreSQL

Pour installer PostgreSQL sur OpenBSD c’est assez simple vu que c’est disponible directement dans les dépots :

pkg_add postgresql-server

Sur OpenBSD, il faut initier le serveur pour pouvoir le démarrer la première fois, ça se fait de cette façon :

su - _postgresql
cd /var/postgresql/
initdb --locale=en_US.UTF-8 -D /var/postgresql/data/ -U postgres --auth=scram-sha-256 --pwprompt --encoding=UTF-8

Ensuite on peut démarrer le service et commencer à créer des utilisateurs et des schémas.

rcctl enable postgresql
rcctl start postgresql

psql -U postgres -W
CREATE USER _smtpd;
CREATE USER _dovecot;
CREATE USER vmail;

CREATE DATABASE vmail;
ALTER DATABASE vmail OWNER TO vmail;

\c vmail
CREATE TABLE public.credentials (
    id SERIAL NOT NULL,
    email character varying(255) DEFAULT ''::character varying NOT NULL,
    password character varying(255) DEFAULT ''::character varying NOT NULL
);
ALTER TABLE public.credentials OWNER TO vmail;

CREATE TABLE public.domains (
    id SERIAL NOT NULL,
    domain character varying(255) DEFAULT ''::character varying NOT NULL
);
ALTER TABLE public.domains OWNER TO vmail;

CREATE TABLE public.virtuals (
    id SERIAL NOT NULL,
    email character varying(255) DEFAULT ''::character varying NOT NULL,
    destination character varying(255) DEFAULT ''::character varying NOT NULL
);
ALTER TABLE public.virtuals OWNER TO vmail;

GRANT CONNECT ON DATABASE vmail TO _smtpd;
GRANT CONNECT ON DATABASE vmail TO _dovecot;

GRANT USAGE ON SCHEMA public TO _smtpd;
GRANT USAGE ON SCHEMA public TO _dovecot;

GRANT SELECT ON TABLE public.credentials TO _smtpd;
GRANT SELECT ON TABLE public.credentials TO _dovecot;

GRANT SELECT ON TABLE public.domains TO _smtpd;
GRANT SELECT ON TABLE public.domains TO _dovecot;

GRANT SELECT ON TABLE public.virtuals TO _smtpd;
GRANT SELECT ON TABLE public.virtuals TO _dovecot;

\q

Ensuite il faut qu’on autorise les utilisateurs à se connecter à la base en mode peer, ça se fait dans /var/postgresql/data/pg_hba.conf

# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             postgres                                scram-sha-256
local   all             all                                     peer
# IPv4 local connections:
host    all             all             127.0.0.1/32            scram-sha-256
# IPv6 local connections:
host    all             all             ::1/128                 scram-sha-256
# Allow replication connections from localhost, by a user with the
# replication privilege.
local   replication     all                                     scram-sha-256
host    replication     all             127.0.0.1/32            scram-sha-256
host    replication     all             ::1/128                 scram-sha-256

Pour finir on redémarre juste le service PostgreSQL :

 rcctl restart postgresql

OpenSMTPD

OpenSMTPD est préinstallé sur OpenBSD, il faut donc installer uniquement les extensions qui nous intéresse, en l’occurrence celles pour rspamd et PostgreSQL.‌‌

Du coup on installe les paquets opensmtpd-filter-rspamd et opensmtpd-extras-pgsql.

pkg_add opensmtpd-filter-rspamd opensmtpd-extras-pgsql

Maintenant on va toucher au fichier de configuration.

/etc/mail/smtpd.conf‌‌

Dégagez tout et mettez cette configuration :

# This is the smtpd server system-wide configuration file. # See smtpd.conf(5) for more information.

srs key CléARemplacer
srs key backup CléARemplacer 

## Certs 

pki domain.tld cert ""
pki domain.tld key ""

table domains postgres:/etc/mail/pgsql.conf
table virtuals postgres:/etc/mail/pgsql.conf
table passwd postgres:/etc/mail/pgsql.conf
table sndrs postgres:/etc/mail/sndrs-pgsql.conf

filter   "rdns" phase connect match   !rdns disconnect "550 DNS error"
filter "fcrdns" phase connect match !fcrdns disconnect "550 DNS error"
filter "rspamd" proc-exec "filter-rspamd"

## Ports to listen on, and how to listen on them

listen on egress port 25 tls pki domain.tld hostname server.domain.tld filter { "rdns", "fcrdns", "rspamd" }
listen on egress port 465 smtps pki domain.tld hostname server.domain.tld mask-src received-auth senders <sndrs> auth <passwd> filter "rspamd"
listen on egress port submission tls-require pki domain.tld hostname server.domain.tld mask-src received-auth senders <sndrs> auth <passwd> filter "rspamd"

action "dovecotFinal" lmtp "/var/dovecot/lmtp" rcpt-to virtual <virtuals>
action "send" relay srs

match from any for domain <domains> action "dovecotFinal"
match from auth for any action "send"
match from local for any action "send"

Remplacez server.domain.tld par le FQDN de votre serveur et mettez votre certificat TLS public et la clé privée.Remplacez aussi les domain.tld par votre domaine si vous utilisez un certificat TLS wildcard, si votre certificat est standalone, vous pouvez remplacer par votre FQDN. (Les clés publiques/privées se collent dans les lignes pki)Remplacez également les clés SRS, celles-ci sont de préférence à changer tous les ans, elles permettent à ce que les mails transférées sous certaines conditions ne soient pas considérés comme du spam.(elles se génèrent comme suit : head -c 30 /dev/urandom | base64 )

Le dernier match de la configuration autorise tout envoi de mail depuis localhost, si vous n’êtes pas à l’aise avec cette idée, vous pouvez supprimer la ligne.

Bien sûr il faut dire à OpenSMTPD quelle base de données utiliser et aussi avec quels identifiants… Et bien ça se passe dans /etc/mail/pgsql.conf !

# PostgreSQL
conninfo host='/tmp'
dbname='vmail'
user='_smtpd'

query_alias SELECT destination FROM virtuals WHERE email=$1;
query_credentials SELECT email, password FROM credentials WHERE email=$1;
query_domain SELECT domain FROM domains WHERE domain=$1;

EDIT 09/03/2023 : Grâce à Tobias Fiebig, j’ai maintenant une façon fiable d’empêcher le spoofing des adresses du champ “From” !

J’ai mis à jour le smtpd.conf plus haut mais en plus de ça il faut rajouter un fichier /etc/mail/sndrs-pgsql.conf.

# PostgreSQL
conninfo host='/tmp' dbname='vmail' user='_smtpd'

query_mailaddrmap with t as (SELECT REGEXP_REPLACE( $1,'[+]([^@])+\@', '\@' )  as addr) select virtuals.email as mail from virtuals join t on REGEXP_REPLACE(virtuals.destination, '[+]([^@])+\@', '\@' ) = t.addr JOIN domains AS vd ON virtuals.email LIKE CONCAT('%',vd.domain,'%') UNION select email from credentials join t on credentials.email = t.addr;

Cette commande SQL c’est de la pure magie noire, en gros elle permet de donner une liste de tous les alias lié à une adresse mail et va donc nous permettre de définir la liste des alias autorisés à être utilisé par un tel user logué.

Et bien sûr c’est compatible RFC 5233, que du bonheur.


Hop là, pour OpenSMTPD c’est bon !

rspamd

Maintenant on attaque rspamd, pareil la version sur les dépôts

pkg_add rspamd

Dans /etc/rspamd/local.d/redis.conf on configure comment accéder au service Redis :

servers = "127.0.0.1";
password = monsupermdp

Et dans /etc/rspamd/override.d/dkim_signing.conf on configure les clés qui vont servir à signer les mails pour DKIM.Mettez votre domaine et le path que vous voulez (idem pour le selecteur), on génère les clés juste après.

allow_username_mismatch = true;
selector = "main";
use_esld = false;
domain {
        domain.tld {
                path = "path to key";
                selector = "selector";
        }
        domain.tld {
                path = "path to key";
                selector = "selector";
        }
        domain.tld {
                path = "path to key";
                selector = "selector";
        }
}

On peut aussi setup les reports DMARC (ici /etc/rspamd/local.d/dmarc.conf) dans le cas où on a gardé la dernière ligne de configuration d’OpenSMTPD

reporting {
    # Required attributes
    enabled = true; # Enable reports in general
    email = 'email@domain.tld'; # Source of DMARC reports
    domain = 'domain.tld'; # Domain to serve
    org_name = 'Organisation Name'; # Organisation
    # Optional parameters
    #bcc_addrs = ["postmaster@example.com"]; # additional addresses to copy on reports
    report_local_controller = false; # Store reports for local/controller scans (for testing only)
    helo = 'rspamd.localhost'; # Helo used in SMTP dialog
    smtp = '127.0.0.1'; # SMTP server IP
    smtp_port = 25; # SMTP server port
    from_name = 'Rspamd'; # SMTP FROM
    msgid_from = 'rspamd'; # Msgid format
    max_entries = 1k; # Maxiumum amount of entries per domain
    keys_expire = 2d; # Expire date for Redis keys
    #only_domains = '/path/to/map'; # Store reports merely from those domains
  }

Vous pouvez ensuite ajouter cette ligne à votre crontab :

30 1 * * * /usr/local/bin/rspamadm dmarc_report

Redis

Pensez à configurer redis.

rcctl enable redis
rcctl start redis
redis-cli
CONFIG SET requirepass "mypassword"

DKIM

La pour le coup c’est super simple :

openssl genrsa -out /etc/mail/dkim-domain.tld-private.key 2048 
openssl rsa -in /etc/mail/dkim-domain.tld-private.key -pubout -out /etc/mail/dkim-domain.tld-public.key

Et hop là toutes mes félicitations vous êtes en possession d’une super clé DKIM !‌‌

La deuxième commande génère la clé publique que vous pouvez utiliser pour faire votre record DKIM sur votre zone DNS.

Attention, n’essayez pas de générer des clés au delà de 2048 bits, quasiment aucun serveur mail ne sera en mesure de les lire.Si votre provider DNS ne supporte pas les clés 2048 bits, vous pouvez essayer de passer en 1024.

Dovecot

Car lire ses mails c’est quand même pratique :')

pkg_add dovecot-pigeonhole dovecot-postgresql dovecot

Avant de commencer à configurer il faut autoriser dovecot à bypass la limite de fichiers ouverts dans OpenBSD via /etc/login.conf

dovecot:\
        :openfiles-cur=1024:\
        :openfiles-max=2048:\
        :tc=daemon:

Et ensuite on édite une nouvelle configuration ici : /etc/dovecot/dovecot.conf

ssl = required
ssl_cert = </path/to/certificate
ssl_key  = </path/to/private/key

ssl_min_protocol = TLSv1.2
ssl_prefer_server_ciphers = yes

disable_plaintext_auth = yes

protocols = lmtp imap

service lmtp {
        unix_listener lmtp {
                user  = vmail
                group = vmail
        }
}

service imap-login {
        inet_listener imaps {
                port = 993
        }
}

passdb {
  driver = sql
  args = /etc/dovecot/dovecot-sql.conf.ext
}

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

mail_location = maildir:/home/vmail/%u/MailDir

protocol imap {
		mail_plugins = $mail_plugins imap_sieve
}

protocol lmtp {
		mail_plugins = $mail_plugins sieve
}

plugin {
        sieve_plugins = sieve_imapsieve sieve_extprograms
        sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment

        imapsieve_mailbox1_name = Junk
        imapsieve_mailbox1_causes = COPY APPEND
        imapsieve_mailbox1_before = file:/usr/local/lib/dovecot/sieve/report-spam.sieve

        imapsieve_mailbox2_name = *
        imapsieve_mailbox2_from = Junk
        imapsieve_mailbox2_causes = COPY
        imapsieve_mailbox2_before = file:/usr/local/lib/dovecot/sieve/report-ham.sieve

        imapsieve_mailbox3_name = Inbox
        imapsieve_mailbox3_causes = APPEND
        imapsieve_mailbox3_before = file:/usr/local/lib/dovecot/sieve/report-ham.sieve

        sieve_pipe_bin_dir = /usr/local/lib/dovecot/sieve
}

Vous remarquerez un user vmail dans la config… Il existe pas ? Normal ! On va remédier à ça.

mkdir /home/vmail
useradd -m -c "Virtual Mail" -d /home/vmail -s /sbin/nologin vmail
chown -R vmail: /home/vmail

Fichier de configuration suivant pour la connexion à PostgreSQL : /etc/dovecot/dovecot-sql.conf.ext

# Database driver: mysql, pgsql, sqlite
driver = pgsql
connect = host=/tmp dbname=vmail user=_dovecot
default_pass_scheme = BLF-CRYPT
password_query = \ 
	SELECT email, password \ 
    	FROM credentials WHERE email = '%u'

On pensera également à rajouter les mécanismes d’apprentissage pour rspamd :

cd /usr/local/lib/dovecot/sieve

cat <<EOF > report-ham.sieve
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];

if environment :matches "imap.mailbox" "*" {
  set "mailbox" "${1}";
}

if string "${mailbox}" "Trash" {
  stop;
}

if environment :matches "imap.user" "*" {
  set "username" "${1}";
}

pipe :copy "sa-learn-ham.sh" [ "${username}" ];
EOF

cat <<EOF > report-spam.sieve
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];

if environment :matches "imap.user" "*" {
  set "username" "${1}";
}

pipe :copy "sa-learn-spam.sh" [ "${username}" ];
EOF

cat <<EOF > sa-learn-ham.sh
#!/bin/sh
exec /usr/local/bin/rspamc -d "${1}" learn_ham
EOF

cat <<EOF > sa-learn-spam.sh
#!/bin/sh
exec /usr/local/bin/rspamc -d "${1}" learn_spam
EOF

rcctl restart dovecot

sievec report-ham.sieve
sievec report-spam.sieve

chmod 755 sa-learn-ham.sh
chmod 755 sa-learn-spam.sh

Presque fini !

On peut commencer à créer des utilisateurs, des domaines etc…Pour ça je vous conseille d’avoir 2 terminaux ouvert :

  • Un sur le shell postgres psql -U postgres- Un autre ouvert de côté

Côté postgres vous pouvez vous connecter à la base vmail avec la commande \c vmail.

De l’autre côté, vous pouvez générer le hash de mot de passe de votre premier compte avec cette commande : doveadm pw -s BLF-CRYPT -r 9

Celà devrait vous donner un résultat du type :{BLF-CRYPT}$2y$09$Kpm01vaz0bGzXKRZ5D2PoumTRE.ihmKktlcJiToVTfWpvmlhT6XMu

Pour que ET OpenSMTPD ET Dovecot puissent comprendre ce résultat, on va le modifier de la façon suivante :

  • Retirer l’entête {BLF-CRYPT}
  • Changer l’entête du hash de $2y$09$ en $2b$09$

Pour la deuxième opération, elle est à faire car OpenSMTPD ne reconnait pas l’en-tête $2y$ alors que $2b$ si, et vu que $2b$ et $2y$ sont de même type, on peut les interchanger sans problème.

Notre hash ressemblera donc à ça :$2b$09$Kpm01vaz0bGzXKRZ5D2PoumTRE.ihmKktlcJiToVTfWpvmlhT6XMu

On peut repasser sur le shell postgres :

INSERT INTO credentials (email, password) VALUES ('email@domain.tld', 'monSuperHash');``INSERT INTO virtuals (email, destination) VALUES ('email@domain.tld', 'vmail');``INSERT INTO domains (domain) VALUES ('domain.tld');

\q

Maintenant on redémarre tous les services :

rcctl enable smtpd dovecot rspamd
rcctl restart smtpd dovecot rspamd

Et on vérifie que la commande hostname ressorte bien un FQDN complet, autrement pensez à le corriger :)Hop là et en théorie votre serveur mail est normalement fonctionnel !

DNS

Quelques petits records DNS à ajouter sur la zone de votre domaine.

Premièrement un A et un AAAA pointant sur votre serveur, ça parait con mais je préfère le mentionner.‌‌ Ensuite un MX pointant sur le FQDN de votre serveur.

Maintenant commence la partie fun, DKIM, SPF et DMARC

# DKIM
main._domainkey.domain.tld. TXT "v=DKIM1; k=rsa; t=s; s=email; h=sha256; p=<public-dkim-key>"

# SPF
domain.tld. TXT "v=spf1 mx -all"

# DMARC
_dmarc.domain.tld. TXT "v=DMARC1; p=none"

Hop là normalement ça devrait être bon.

Fini !

Voilà, vous voici avec votre propre serveur mail !

Vous pouvez tester son fonctionnement via un service comme mail-tester et ensuite profiter :)Merci à leonekmi, luclu7 et ramle pour la relecture ! :)