Serveur de messagerie : Postfix, Dovecot et Rspamd
Quitter

Leçon 1 sur 12 · Fondamentaux SMTP

Le voyage d'un e-mail : SMTP, submission et IMAP

40 min Python 3.12 (Debian, git, pip)

Objectif

Comprendre ce qui se passe entre le clic sur « Envoyer » et l'arrivée du message dans une boîte, savoir parler SMTP à la main à un serveur et lire l'historique d'un message dans ses en-têtes.

L'essentiel

  • Quatre rôles : le MUA (Thunderbird, Outlook, un téléphone) écrit ; le MSA reçoit l'envoi authentifié (port 587 ou 465) ; le MTA (Postfix) transporte de serveur à serveur (port 25) ; le MDA (Dovecot) range dans la boîte, que le client lit en IMAP (port 993).
  • Pour trouver le serveur du destinataire, le MTA demande au DNS l'enregistrement MX du domaine.
  • Une session SMTP tient en cinq commandes : EHLO, MAIL FROM, RCPT TO, DATA, QUIT.
  • L'enveloppe (MAIL FROM, RCPT TO) sert au transport ; les en-têtes (From:, To:, Subject:) sont ce que l'utilisateur voit. Ils peuvent être différents.
  • Chaque serveur traversé ajoute une ligne Received: en haut : on lit donc l'historique de bas en haut.

1. Les acteurs et les ports

 poste de l'agence de Fès                      Internet                       ml-mail-01.masterlinux.lab
┌───────────────┐  587 (STARTTLS)   ┌──────────────┐   25 (SMTP)   ┌─────────────────────────────┐
│ MUA           │ ── + mot de passe ─► │ MSA/MTA      │ ─────────────► │ MTA Postfix ─► MDA Dovecot │
│ Thunderbird   │                   │ smtp.gmail…  │  (MX du domaine) │        boîte Maildir          │
└───────────────┘                   └──────────────┘                  └──────────────┬──────────────┘
                                                                         IMAP 993 (TLS) │
                                                                  téléphone du support ◄┘
Port Nom (/etc/services) Rôle Chiffrement
25 smtp serveur à serveur (relais MX) STARTTLS facultatif
587 submission envoi par les utilisateurs, avec authentification STARTTLS obligatoire
465 submissions envoi par les utilisateurs, TLS dès la connexion TLS implicite
143 imap lecture des boîtes STARTTLS
993 imaps lecture des boîtes TLS implicite
110 / 995 pop3 / pop3s ancien protocole de relève —

Le port 25 sert entre serveurs. Beaucoup d'opérateurs (au Maroc comme ailleurs) bloquent le port 25 sortant des connexions domestiques : un utilisateur qui envoie doit passer par 587 ou 465, authentifié.

2. Trouver le serveur du destinataire : le MX

dig MX masterlinux.lab +short
# 10 mail.masterlinux.lab.          ← priorité 10 (plus petit = préféré)
# 20 mail2.masterlinux.lab.         ← secours
dig A mail.masterlinux.lab +short
# 203.0.113.25

Sans MX, l'expéditeur essaie l'enregistrement A du domaine lui-même (règle de repli de la RFC 5321). Un domaine qui ne reçoit jamais de courrier peut publier un MX nul : @ IN MX 0 ..

3. Une session SMTP à la main

Sur un vrai serveur, on teste avec swaks (paquet swaks) ou à la main avec nc ou openssl s_client :

swaks --to support@masterlinux.lab --from ops@masterlinux.lab --server 127.0.0.1 --header "Subject: Test"
openssl s_client -connect mail.masterlinux.lab:25 -starttls smtp -crlf -quiet

Le dialogue, ligne par ligne (C = client, S = serveur) :

S: 220 mail.masterlinux.lab ESMTP Postfix              ← bannière
C: EHLO ml-poste-01
S: 250-mail.masterlinux.lab
S: 250-SIZE 10240000                                    ← taille maximale acceptée
S: 250-STARTTLS                                         ← chiffrement possible
S: 250 8BITMIME
C: MAIL FROM:<ops@masterlinux.lab>                      ← enveloppe : expéditeur (adresse de retour)
S: 250 2.1.0 Ok
C: RCPT TO:<support@masterlinux.lab>                    ← enveloppe : destinataire
S: 250 2.1.5 Ok
C: DATA
S: 354 End data with <CR><LF>.<CR><LF>
C: From: ops@masterlinux.lab                            ← en-têtes (ce que l'utilisateur voit)
C: Subject: Premier envoi
C:                                                      ← ligne vide : fin des en-têtes
C: Bonjour.
C: .                                                    ← un point seul : fin du message
S: 250 2.0.0 Ok: queued as 3F2A61C0D2                   ← identifiant dans la file
C: QUIT
S: 221 2.0.0 Bye

Les codes de réponse se lisent au premier chiffre : 2xx réussi, 3xx continuer, 4xx échec temporaire (l'expéditeur réessaiera), 5xx échec définitif (retour à l'expéditeur). Le code étendu (5.1.1, 4.7.1…) précise la cause : 5.1.1 = boîte inconnue, 5.7.1 = refusé par une règle de sécurité.

Dans les labs de cette formation

La machine de lab n'a pas de réseau. Elle fait tourner ml-smtpd, un serveur SMTP d'entraînement qui lit le vrai fichier /etc/postfix/main.cf et répond avec les messages de Postfix. Il écoute sur 127.0.0.1:25 et :587, livre les domaines locaux en Maildir et écrit /var/log/mail.log.

# 1. comme sendmail -bs : une session SMTP sur l'entrée standard
printf 'EHLO lab\r\nMAIL FROM:<ops@masterlinux.lab>\r\nRCPT TO:<support@masterlinux.lab>\r\nQUIT\r\n' | ml-smtpd --stdio

# 2. par le réseau local, avec Python (set_debuglevel affiche tout le dialogue)
python3 -c 'import smtplib; s=smtplib.SMTP("127.0.0.1", 25); s.set_debuglevel(1); s.noop(); s.quit()'

# 3. à la main avec bash (redirection /dev/tcp)
exec 3<>/dev/tcp/127.0.0.1/25; head -1 <&3; printf 'QUIT\r\n' >&3; cat <&3

4. Enveloppe contre en-têtes

L'enveloppe ressemble à l'adresse écrite sur une lettre postale ; les en-têtes sont l'en-tête de la lettre à l'intérieur. Le facteur ne lit que l'enveloppe.

Enveloppe (SMTP) En-têtes (contenu)
Expéditeur MAIL FROM:<bounces@masterlinux.lab> From: Support <support@masterlinux.lab>
Destinataire RCPT TO:<admin@masterlinux.lab> To:, Cc: (et rien pour Bcc)
Qui l'utilise les serveurs, SPF, les retours d'erreur l'utilisateur, DMARC, les filtres
Trace à l'arrivée Return-Path: ajouté par le serveur final inchangés

C'est ce qui permet à une plateforme d'envoi de recevoir les retours d'erreur (bounces@…) tout en affichant l'adresse de la boutique. C'est aussi ce que les usurpateurs exploitent : d'où SPF, DKIM et DMARC (niveau 4).

5. Lire les lignes Received

Received: from mail-ed1-f45.google.com (mail-ed1-f45.google.com [209.85.208.45])   ← 3. reçu par NOTRE serveur
        by mail.masterlinux.lab (Postfix) with ESMTPS id 4B7F21C0A2 …
Received: by mail-ed1-f45.google.com with SMTP id …                                  ← 2. transfert interne Google
Received: from [192.168.1.23] ([41.250.88.17]) by smtp.gmail.com with ESMTPSA …        ← 1. envoi du client
  • ESMTPS = chiffré (TLS), ESMTPA = authentifié, ESMTPSA = les deux.
  • Dans from X ([IP]), le nom X est déclaré par le client (il peut mentir) ; l'IP entre crochets est vue par le serveur (fiable si ce serveur est de confiance).
  • 192.168.1.23 est l'adresse privée du poste derrière la box ; 41.250.88.17 est l'adresse publique vue par Gmail.
grep -n '^Received:' /root/recu.eml            # les lignes de début
awk '/^Received:/,/;/' /root/recu.eml          # chaque Received complet (jusqu'à la date)

Cas réel

Un lundi matin, l'équipe support de la boutique MasterLinux reçoit une réclamation « je n'ai jamais reçu votre réponse ». En relisant les en-têtes du message envoyé, l'administrateur voit que le poste du support envoyait en ESMTP (sans A) vers le port 25 d'un ancien serveur : le message partait sans authentification et Gmail le classait en spam. Passer le client de messagerie en 587 + STARTTLS + authentification a réglé le problème. Lire les Received avant de toucher à la configuration fait gagner des heures.

Pièges fréquents

  • Confondre 25 et 587 : un client de messagerie configuré sur 25 marche au bureau puis échoue en 4G (port bloqué par l'opérateur).
  • Croire l'en-tête From: : il est écrit par l'expéditeur, comme le nom après from dans Received. Seules les IP ajoutées par vos serveurs sont fiables.
  • Oublier \r\n en SMTP à la main : certains serveurs refusent les fins de ligne seules (\n) — c'est même la base de l'attaque « SMTP smuggling » de 2023-2024, corrigée dans Postfix par smtpd_forbid_bare_newline.
  • Lire les Received de haut en bas : le premier est le plus récent.

Lab

Sur la machine de lab, ml-smtpd joue le rôle de ml-mail-01 pour le domaine masterlinux.lab. Vous allez : envoyer un premier message par SMTP, envoyer un message dont l'enveloppe diffère de l'en-tête, provoquer un refus 550 5.1.1, retrouver l'IP d'origine d'un message réel, et récapituler les ports de la messagerie.

Quiz

  1. Quel enregistrement DNS indique le serveur qui reçoit le courrier d'un domaine ?
  2. Quelle est la différence entre MAIL FROM et l'en-tête From: ?
  3. Que signifie un code de réponse qui commence par 4 ?
  4. Pourquoi un utilisateur envoie-t-il sur 587 et non sur 25 ?
  5. Dans quel ordre lit-on les lignes Received: ?
Réponses
  1. L'enregistrement MX (avec sa priorité ; plus petit = préféré).
  2. MAIL FROM fait partie de l'enveloppe SMTP (adresse de retour des erreurs, utilisée par SPF) ; From: est un en-tête du contenu, affiché à l'utilisateur.
  3. Un échec temporaire : le serveur expéditeur garde le message en file et réessaie plus tard.
  4. Le port 587 (submission) exige l'authentification et le chiffrement ; le port 25 est réservé aux échanges entre serveurs et souvent bloqué en sortie chez les opérateurs.
  5. De bas en haut : chaque serveur ajoute sa ligne au-dessus des précédentes.

Exercices pratiques

À réaliser sur Python 3.12 (Debian, git, pip)

Faites ce lab sur votre machine

Le terminal, c’est le vôtre : une VM Linux (VirtualBox, VMware, Hyper-V, WSL…) ou Docker/Podman. L’outil ml prépare l’exercice et corrige votre travail ; la leçon se valide toute seule ici.

Créez un compte gratuit pour obtenir votre commande personnelle et faire valider vos labs automatiquement.

Créer mon compte

Tâches à réaliser

  1. Par SMTP sur 127.0.0.1 port 25, envoyez un message de ops@masterlinux.lab à support@masterlinux.lab avec le sujet « Premier envoi ». Il doit arriver dans la boîte Maildir /var/mail/vhosts/masterlinux.lab/support/.
    Afficher un indicepython3 -c 'import smtplib; s=smtplib.SMTP("127.0.0.1", 25); s.set_debuglevel(1); s.sendmail("ops@masterlinux.lab", ["support@masterlinux.lab"], "Subject: Premier envoi\r\n\r\nBonjour"); s.quit()'
    Voir la solution
    printf 'EHLO ml-poste-01\r\nMAIL FROM:<ops@masterlinux.lab>\r\nRCPT TO:<support@masterlinux.lab>\r\nDATA\r\nFrom: ops@masterlinux.lab\r\nTo: support@masterlinux.lab\r\nSubject: Premier envoi\r\n\r\nBonjour depuis le lab.\r\n.\r\nQUIT\r\n' | ml-smtpd --stdio

    Vérification : grep -l "^Subject: Premier envoi" /var/mail/vhosts/masterlinux.lab/support/new/* 2>/dev/null | xargs -r grep -l "^Return-Path: <ops@masterlinux.lab>" | grep -q .

  2. Envoyez à admin@masterlinux.lab un message dont l'expéditeur d'enveloppe (MAIL FROM) est bounces@masterlinux.lab mais dont l'en-tête From: affiche support@masterlinux.lab, avec le sujet « Enveloppe ».
    Afficher un indiceL'enveloppe, c'est MAIL FROM ; l'en-tête From: s'écrit après DATA. Le serveur note l'enveloppe dans Return-Path.
    Voir la solution
    printf 'EHLO ml-poste-01\r\nMAIL FROM:<bounces@masterlinux.lab>\r\nRCPT TO:<admin@masterlinux.lab>\r\nDATA\r\nFrom: Support MasterLinux <support@masterlinux.lab>\r\nTo: admin@masterlinux.lab\r\nSubject: Enveloppe\r\n\r\nTest enveloppe.\r\n.\r\nQUIT\r\n' | ml-smtpd --stdio

    Vérification : f=$(grep -l "^Subject: Enveloppe" /var/mail/vhosts/masterlinux.lab/admin/new/* 2>/dev/null | head -1); [ -n "$f" ] && grep -q "^Return-Path: <bounces@masterlinux.lab>" "$f" && grep -Eq "^From:.*support@masterlinux\.lab" "$f"

  3. Tentez d'écrire à stagiaire@masterlinux.lab (boîte qui n'existe pas) et enregistrez la réponse complète du serveur à RCPT TO dans /root/code-rejet.txt.
    Afficher un indicePassez la session dans ml-smtpd --stdio et filtrez la ligne qui commence par 550.
    Voir la solution
    printf 'EHLO ml-poste-01\r\nMAIL FROM:<ops@masterlinux.lab>\r\nRCPT TO:<stagiaire@masterlinux.lab>\r\nQUIT\r\n' | ml-smtpd --stdio | grep "^550" > /root/code-rejet.txt

    Vérification : grep -q "^550 5\.1\.1 <stagiaire@masterlinux\.lab>" /root/code-rejet.txt && grep -q "NOQUEUE: reject: RCPT.*to=<stagiaire@masterlinux.lab>" /var/log/mail.log

  4. Le message /root/recu.eml vient d'un client. Écrivez dans /root/origine.txt l'adresse IP publique de l'ordinateur sur lequel il a été écrit (une seule ligne).
    Afficher un indiceLes lignes Received se lisent de bas en haut ; 192.168.x.x est une adresse privée.
    Voir la solution
    echo 41.250.88.17 > /root/origine.txt

    Vérification : grep -qx "41\.250\.88\.17" /root/origine.txt

  5. Dans /root/ports.txt, écrivez une ligne « port service » pour chacun de ces services : 25 smtp, 465 submissions, 587 submission, 143 imap et 993 imaps.
    Afficher un indiceComparez avec grep -Ew '25|465|587|143|993' /etc/services quand le fichier existe.
    Voir la solution
    printf '25 smtp\n465 submissions\n587 submission\n143 imap\n993 imaps\n' > /root/ports.txt

    Vérification : f=/root/ports.txt; grep -Eqx "25[[:space:]]+smtp" $f && grep -Eqx "465[[:space:]]+submissions" $f && grep -Eqx "587[[:space:]]+submission" $f && grep -Eqx "143[[:space:]]+imap" $f && grep -Eqx "993[[:space:]]+imaps" $f