9. Fonctionnalités prises en charge

Les fonctionnalités de niveau protocole et politique que Pepsi prend en charge, avec la RFC qui régit chacune : la machinerie SMTP et d’authentification, la cryptographie de bout en bout, et la surface d’exploitation qui entoure les deux. L”Index des RFC met en correspondance chaque RFC avec le code et la configuration ; les chapitres par programme indiquent quel programme réalise chaque fonctionnalité.

9.1. Réception SMTP entrante

pepsi-ingress est un serveur SMTP conforme aux normes (RFC 5321) avec ces extensions :

  • Sécurité du transport. En clair, mise à niveau STARTTLS (RFC 3207) et TLS implicite (RFC 8314, par exemple le port submissions 465) — sélectionnés par listener via MODE. Des listeners de soumission (RFC 6409, SUBMISSION = yes) peuvent être liés — voir Soumission de messages.

  • Transfert du corps du message. DATA classique et CHUNKING/BDAT (RFC 3030) pour les messages volumineux. BINARYMIME n’est pas annoncé, et un MAIL FROM portant BODY=BINARYMIME est refusé par 501 5.5.4.

  • Annonce et application de SIZE (MAX_MESSAGE_SIZE ; taille excessive → 552).

  • PIPELINING et ENHANCEDSTATUSCODES.

  • 8BITMIME (RFC 6152) et SMTPUTF8 (RFC 6531) — voir Contenu internationalisé et 8 bits.

  • DSN (RFC 3461) — voir Notifications d’état de remise.

  • SMTP AUTH (RFC 4954) pour la soumission — voir Authentification du client.

  • Politique d’acceptation : le courrier n’est accepté que pour ACCEPTED_DOMAINS (les autres sont rejetés comme relayage avec 550) ; la boîte aux lettres <Postmaster> sans domaine est toujours acceptée (RFC 5321 §4.5.1) ; les destinataires qui sont des jetons SRS valides sont décodés en sens inverse et relayés (voir Sender Rewriting Scheme (SRS)).

  • Durabilité : le client ne reçoit 250 qu’une fois la ligne validée en base ; si la base de données est indisponible, le client reçoit un 451 transitoire et réessaie.

9.2. En-têtes de trace

L’ingress ajoute en tête un en-tête Received: RFC 5321 §4.4 à chaque message avant l’authentification (de sorte que le sceau ARC le couvre). La clause with note le type de transmission — SMTP, ESMTP, ESMTPS, ESMTPA ou ESMTPSA — conformément à la RFC 3848 (l”ESMTPA nu, authentifié mais non sur TLS, est la socket UNIX de soumission locale), et une clause for nomme le destinataire pour les transactions à destinataire unique.

9.3. Authentification du client

Chaque listener peut authentifier le client connecté et marquer son courrier comme d’origine locale (ce qui lui permet de relayer plus loin vers n’importe quel domaine et appose ESMTPSA dans la trace Received:). Une session est authentifiée lorsque l’un quelconque de quatre mécanismes par listener réussit :

  • l’IP du pair est dans le MYNETWORKS du listener (considérée digne de confiance d’emblée) ;

  • un échange SMTP AUTH réussit (RFC 4954, PLAIN/LOGIN), vérifié auprès d’un backend SASL configuré (SASL_TYPE/SASL_PATH ; la socket auth-client de Dovecot aujourd’hui). AUTH n’est annoncé et accepté que via TLS — une tentative en clair est refusée par 504 5.5.4, le code que la RFC 4954 §4 prescrit pour un mécanisme qui « exige une couche de chiffrement » (le §1 déprécie l’ancien 538) ;

  • un certificat client TLS présenté correspond à une clé/épingle de CA configurée (TLS_AUTH_CLIENT) ;

  • le pair d’une socket UNIX est identifié par SO_PEERCRED (AUTH_PEERCRED, la socket de soumission locale) et son login est associé à au moins une adresse. Celui-ci porte un nom d’utilisateur, il alimente donc la même application de USERNAME_MAP qu’une session SASL.

Ceci est distinct de l”Authentification à la bordure ci-dessous, qui vérifie le message (SPF/DKIM/DMARC) indépendamment de la manière dont — ou du fait que — le client lui-même s’est authentifié. Voir pepsi-ingress.

9.4. Soumission de messages

Un listener marqué SUBMISSION = yes se comporte comme un Message Submission Agent (RFC 6409) plutôt que comme un MX :

  • l’authentification est obligatoire — un MAIL FROM non authentifié est refusé par 530 5.7.0 (donc l’indicateur exige l’un des mécanismes d’authentification client ci-dessus, et TLS sur tout écouteur qui n’est pas une socket locale du domaine UNIX) ; et

  • des corrections de soumission sont appliquées à chaque message accepté : un Date: manquant (§8.2) et un Message-ID: manquant (§8.3) sont ajoutés avant le stockage du message ; et

  • l”application de l’identité de soumission (§6) via USERNAME_MAP : le MAIL FROM d’enveloppe et l’en-tête From: doivent être des adresses que l’utilisateur authentifié a le droit d’utiliser (sinon 550), et un From: autorisé qui diffère de l’identité canonique de l’utilisateur obtient un en-tête Sender: qui la nomme (§8.1).

L’indicateur n’a aucun effet sur un listener MX.

9.5. Authentification à la bordure

Avant de stocker un message, l’ingress l’authentifie afin que le destinataire final puisse encore voir le verdict que Pepsi a atteint à la bordure (la redirection cassera les signaux SPF/DKIM originaux) :

  • SPF (RFC 7208) sur l’identité MAIL FROM en utilisant l’IP du client.

  • Vérification de signature DKIM (RFC 6376 / RFC 8463).

  • Évaluation DMARC (RFC 7489), avec application facultative au moment SMTP (DMARC_ENFORCE → 550 sur un échec certain sous une politique quarantine/reject). L’authentification est sinon fail-open : une erreur DNS ou d’analyse est notée et le courrier accepté.

  • iprev / FCrDNS (RFC 8601) — le PTR du client confirme-t-il en sens direct vers son IP.

  • Un en-tête Authentication-Results (RFC 8601) notant tout ce qui précède, estampillé du nom d’hôte de l’ingress comme authserv-id.

9.6. Authenticated Received Chain (ARC)

L’étape pepsi-stage-arc implémente ARC (RFC 8617). Elle vérifie toute chaîne ARC entrante et scelle le message en tant que notre ADMD en ajoutant en tête un ensemble ARC-Authentication-Results / ARC-Message-Signature / ARC-Seal, signé avec l’unique ARC_ALGORITHM (ARC permet une signature par saut). Cela permet à un destinataire en aval de faire confiance au verdict de bordure après que la redirection a cassé l’alignement SPF/DKIM. Le scellement est fail-open. Comme ARC préserve l’authentification d’un expéditeur en amont à travers le saut de redirection, il ne s’applique qu’au courrier que Pepsi reçoit : les soumissions d’origine locale (state.local_origin) sont ignorées — elles sont authentifiées comme domaine auteur par l’étape de signature DKIM — de sorte que le pipeline généré place ARC sur la branche entrante, après la scission state.local_origin.

9.7. Sender Rewriting Scheme (SRS)

L’étape pepsi-stage-srs réécrit l’expéditeur d’enveloppe en une adresse locale d’un SRS_DOMAIN contrôlé par Pepsi qui encode en HMAC l’expéditeur original (le MAC tronqué est encodé en base32, RFC 4648), de sorte que SPF réussit au saut suivant. L’expéditeur nul et une adresse déjà dans le domaine SRS sont laissés inchangés ; une adresse déjà SRS est re-signée sous la forme compacte SRS1. L’ingress effectue le sens inverse : un rebond renvoyé à une adresse SRS valide est vérifié et relayé vers l’expéditeur original, tandis qu’un jeton falsifié ou expiré est rejeté (550).

9.8. Chiffrement et signature de bout en bout

pepsi-stage-encrypt chiffre un message soumis localement pour chaque destinataire, en OpenPGP (PGP/MIME, RFC 3156) ou en S/MIME (CMS, RFC 8551 / 5083), signé avec la clé propre de l”auteur du From:. La signature est placée à l’intérieur du texte chiffré.

ENABLE_PEP (activé par défaut) est un préréglage de valeurs par défaut calqué sur le projet pretty Easy privacy : chaque expéditeur local d’un domaine servi obtient automatiquement une clé OpenPGP, un message n’est signé que lorsqu’il est chiffré (SIGN = encrypted-only), et le courrier en clair porte la clé de l’expéditeur dans un en-tête Autocrypt: mais aucune signature. Toute option écrite explicitement l’emporte toujours ; ENABLE_PEP = no rend la signature opportuniste et désactive la création automatique de clés.

Chaque destinataire chiffré obtient son propre texte chiffré sur sa propre ligne de file d’attente : il n’y a aucune divulgation de la liste des destinataires, aucune clé de contenu partagée entre destinataires, et une fuite de Bcc est structurellement impossible. Les destinataires qui partagent une issue partagent une ligne.

Les clés des destinataires proviennent du magasin de Gestion des clés, alimenté par la couche de découverte (WKD, VKS, DANE, LDAP, moisson depuis le courrier entrant). L’étape elle-même ne fait aucune E/S réseau : un destinataire dont la clé n’est pas en cache met le message en pause jusqu’à ce qu’un service de découverte règle la demande, et un destinataire ayant une entrée récente de cache négatif prend aussitôt le chemin sans clé.

Ce qu’il advient d’un destinataire sans clé relève de la politique — ON_NO_KEY vaut cleartext, secure-link ou bounce, et prend sa valeur par défaut d”ENCRYPT plutôt que d’en porter une propre. ENCRYPT = required ne se dégrade jamais en clair, et aucune issue en clair n’est jamais silencieuse — chacune est journalisée et notée par destinataire dans state.crypto.out, avec le conteneur de contenu réellement employé et l’indication d’une éventuelle rétrogradation.

L’étape s’exécute avant la signature DKIM, de sorte que DKIM couvre les octets réellement transmis ; voir Configuration pour comprendre pourquoi cet ordre n’est pas facultatif.

9.9. Déchiffrement et vérification côté serveur

pepsi-stage-decrypt en est la moitié entrante. Pour un message adressé à un destinataire que cet hôte sert, elle ouvre tout texte chiffré que le message porte, vérifie toute signature qu’il porte, note le verdict et retire tout indicateur de sécurité que l’expéditeur aurait falsifié. L’utilisateur lit du courrier ordinaire dans son client habituel, sans aucun greffon. L’exception est le courrier chiffré vers une clé que l’utilisateur a enregistrée depuis son propre client de messagerie (une clé sous garde du client, dont Pepsi ne détient jamais la moitié privée) : celui-ci est transmis sans être ouvert, pour que le client le déchiffre.

Elle ne s’exécute que pour les destinataires que cet hôte sert, et n’essaie même pas pour les autres. Le déchiffrement réécrit le corps, ce qui invalide la signature DKIM de l’expéditeur ; c’est sans conséquence pour un message sur le point d’être classé dans une boîte aux lettres ici, et ce ne l’est pas pour un message relayé plus loin. Un message comportant les deux sortes de destinataires est scindé, de sorte que la copie réacheminée soit celle qui est arrivée.

Le vocabulaire des verdicts est précis. valid signifie que la signature est saine et que la clé était ancrée — une chaîne X.509 vers une autorité de certification configurée, ou une clé OpenPGP issue d’une source de découverte classée. valid-untrusted signifie saine avec une clé qui n’a pu être rattachée à rien, ce qui est l’état de la majeure partie du courrier signé du monde et n’est jamais rapporté comme valid. invalid, unverifiable et none complètent l’ensemble, et lorsqu’un message porte plusieurs signatures sur le même contenu, le pire d’entre eux l’emporte. (Une signature faite à l’extérieur du chiffré est une classe à part : elle ne répond du message que lorsque rien ne signe le texte en clair, et jamais mieux alors que valid-untrusted — voir pepsi-stage-decrypt.) Seul valid positionne state.signature_verified, ce à quoi pepsi-stage-check-whitelist conditionne une ligne de liste blanche.

Les deux chemins d’échec remettent par défaut, conformément à la posture fail-open de Pepsi sur le SPF, le DKIM et le DMARC entrants : un message que nous n’avons pas pu ouvrir est remis toujours chiffré (l’utilisateur détient peut-être la clé dans son propre client), et une mauvaise signature est un verdict noté plutôt qu’un échec de remise (les listes de diffusion qui réécrivent les corps en produisent sur du courrier parfaitement légitime). Des routes de quarantaine et de rebond existent pour les déploiements qui les veulent.

Le résultat parvient à l’utilisateur de deux façons : un en-tête X-Pepsi-Crypto à la grammaire documentée et — par défaut — un [decrypted][verified] ajouté en tête du Subject dans l’ordre d’imbrication, ce qui est pour la plupart des utilisateurs le seul signal qu’ils verront jamais. Les deux reposent sur la même discipline : chaque champ X-Pepsi-* et chacune de nos propres marques de sujet est retiré du courrier entrant avant que le nôtre ne soit ajouté. Un en-tête privé est falsifiable par définition, et ce retrait est toute la base sur laquelle on peut y croire.

Le matériel de clé qu’un message porte — certificats de signataire S/MIME, parties application/pgp-keys, en-têtes Autocrypt — est lu en mémoire pour vérifier la signature de ce message-là, parce que la plupart du courrier signé porte le certificat qui l’a signé. Lorsque ce n’est pas le cas, le message se met en pause sur une demande de découverte plutôt que de bloquer sur un serveur de clés, de sorte que même le premier message d’un nouveau correspondant obtienne un verdict réel.

L’étape de déchiffrement n’en stocke rien. Tout l’apprentissage des clés de pairs est une étape à part entière, pepsi-stage-autocrypt-learn, placée après le verdict de spam — ce qui est précisément ce qui rend applicable la règle « ignorer les messages que le MUA croit être du spam » du §5.3 d’Autocrypt Level 1 (LEARN_FROM_SPAM, désactivé par défaut). Un pipeline entrant sans cette étape vérifie parfaitement bien les signatures et n’apprend jamais la clé d’un correspondant.

Ce que l’étape de déchiffrement ouvre est déposé en clair — sauf si le destinataire a enregistré sa propre clé de client de messagerie, auquel cas pepsi-stage-reencrypt, placée immédiatement avant la remise locale, le scelle avec cette clé une fois que toutes les étapes qui le lisent ont terminé. Une politique par destinataire (ON_NO_CLIENT_KEY) peut refuser le courrier d’un utilisateur qui n’a pas de telle clé plutôt que de le déposer lisible.

9.10. Gestion, découverte et publication des clés

Aucune des deux étapes cryptographiques ne fait d’E/S réseau propre. Toutes deux puisent dans un magasin de clés du schéma partagé, alimenté dans deux directions : par pepsi-keydisc — une instance de service par méthode de découverte, de sorte qu’un serveur de clés lent ne retarde personne — et par pepsi-stage-autocrypt-learn à partir du courrier qui arrive. Une étape qui a besoin d’une clé dont elle ne dispose pas valide ce qu’elle peut conserver, met le message en pause et met une demande en file ; le service qui répond libère, dans le même aller-retour, chaque message garé sur cette adresse.

  • Sources de découverte, chacune une consultation distincte exécutée simultanément avec son propre rang sur une échelle de confiance : le Web Key Directory (formes avancée et directe), DANE OPENPGPKEY (RFC 7929) et SMIMEA (RFC 8162) — acceptés seulement lorsque le bit AD du résolveur indique que la réponse a été validée par DNSSEC —, le protocole VKS d’un serveur de clés vérificateur, et LDAP. Au-dessous de toutes sur l’échelle se trouvent les deux échelons qui ne sont pas des services de découverte et n’ont aucune instance : le matériel moissonné dans le courrier entrant (certificats de signataire S/MIME, parties application/pgp-keys et en-têtes Autocrypt), et — un échelon plus bas encore, tout en bas — la clé d’un tiers introduite par Autocrypt-Gossip: dans un message arrivé chiffré. Les deux sont écrits sur place par pepsi-stage-autocrypt-learn ; nommer l’un ou l’autre dans SOURCES est refusé.

  • Publication des clés de nos propres utilisateurs : les points de terminaison WKD servis par pepsi-httpd, les enregistrements OPENPGPKEY/SMIMEA imprimés par pepsi-keys et pepsi-setup, et — uniquement sur demande, par identité — le téléversement vers un serveur de clés vérificateur, dont pepsi-stage-vks-confirm répond au courrier de confirmation.

  • Garde. Le matériel privé est stocké enveloppé sous une clé de chiffrement de clés qui réside hors de la base de données, et n’est atteignable que par l’unique rôle de base sous lequel s’exécutent les étapes cryptographiques.

  • Un cache négatif fait que seul le premier message vers ou depuis un correspondant inconnu doit jamais attendre.

Gestion des clés est le chapitre consacré à tout cela ; la référence des options est pepsi.conf(5).

9.12. Signature DKIM sortante

L’étape pepsi-stage-dkim-sign ajoute en tête des signatures DKIM (RFC 6376) — par défaut à la fois une signature RSA-2048 et une Ed25519 (RFC 8463), ou l’une des deux seulement selon [pepsi] DKIM_ALGORITHMS — sous le SIGNING_DOMAIN ou le domaine du From: du message. Gmail, Microsoft 365 et Yahoo ne vérifient pas Ed25519, et Google indique une telle signature comme fail dans ses rapports agrégés DMARC ; DMARC réussit malgré tout grâce à la signature RSA. La signature couvre toujours le corps entier : le tag de longueur de corps l= de la RFC 6376 n’est jamais émis, car les vérificateurs stricts rejettent d’emblée une telle signature au lieu de tolérer la couverture plus courte. La signature est fail-closed : un message dont le domaine de signature n’a pas de clés, ou qui ne peut pas être signé, est marqué failed au lieu d’être transmis non signé. Comme la sélection d’en-têtes se fait de bas en haut, la signature ne perturbe pas les signatures existantes.

9.13. Notifications d’état de remise

Pepsi implémente DSN de bout en bout (RFC 3461 / RFC 3463 / RFC 3464) :

  • L’ingress annonce DSN et valide/stocke RET/ENVID (sur MAIL FROM) et NOTIFY/ORCPT (sur RCPT TO) sous state.dsn.

  • Les étapes de relais propagent ces paramètres à un saut suivant qui annonce aussi DSN, et les omettent sinon (RFC 3461 §6 — Pepsi ne devient pas le relais responsable du DSN).

  • pepsi-stage-bounce émet le rapport sous forme de multipart/report RFC 3464 avec des codes d’état étendus RFC 3463 :

    • un rapport d”échec lorsque NOTIFY demande FAILURE (la valeur par défaut en son absence) — NOTIFY=NEVER abandonne silencieusement ;

    • un rapport de succès (Action: delivered) uniquement lorsque le ORIGINATE_SUCCESS_DSN global est activé et que NOTIFY=SUCCESS a été demandé ;

    • un rapport de délai (Action: delayed) lorsque le DELAY_DSN_AFTER d’une étape de relais s’écoule sur un message encore en file d’attente qui a demandé NOTIFY=DELAY (envoyé au plus une fois).

Un message à expéditeur nul (un rebond) n’est jamais lui-même renvoyé en rebond (RFC 5321 §6.1).

9.14. Contenu internationalisé et 8 bits

L’ingress annonce 8BITMIME (RFC 6152) et SMTPUTF8 (RFC 6531) et note la déclaration BODY=. Comme les capacités d’un saut suivant sont inconnues jusqu’après la connexion, le client SMTP sortant décide par saut :

  • Si le saut prend en charge l’extension, Pepsi ré-annonce BODY=8BITMIME / SMTPUTF8.

  • Sinon il rétrograde : les parties MIME terminales 8 bits sont ré-encodées vers un codage de transfert 7 bits (RFC 2045 — quoted-printable pour text/*, base64 sinon), d’après les octets constatés plutôt que d’après l’encodage déclaré ; les champs d’en-tête UTF-8 sont réécrits en mots encodés RFC 2047, aux trois endroits où le §5 de la RFC 2047 en autorise un (un champ non structuré, une phrase de nom d’affichage, l’intérieur d’un commentaire) ; un paramètre Content-Type / Content-Disposition non ASCII — le nom de fichier d’une pièce jointe, dans n’importe quelle partie MIME — prend la forme RFC 2231 (filename*=UTF-8''caf%C3%A9.txt), et un boundary= non ASCII, qui n’a pas de forme encodée, est remplacé par un nouveau délimiteur ASCII, lignes de délimitation comprises.

  • Les octets rétrogradés sont ensuite retestés : la conversion se fait au mieux sur un message que personne n’a validé, et le §3 de la RFC 6152 interdit « en toutes circonstances » d’offrir du contenu 8 bits à un saut qui n’a pas annoncé 8BITMIME ; un corps encore 8 bits après coup est donc un échec permanent (rebond).

  • Une adresse non ASCII (d’enveloppe, ou à l’intérieur d’une adresse d’en-tête) qui ne peut être représentée vers un saut non-SMTPUTF8 est un échec permanent (rebond).

Le ré-encodage du corps casse nécessairement une signature DKIM/ARC couvrant le corps ; c’est inévitable (les capacités sont liées tardivement) et rare.

9.15. Relais sortant

Deux étapes de relais interchangeables envoient le courrier vers l’extérieur :

  • pepsi-stage-relay-to-internet — remise directe vers le MX (RFC 5321 §5) : sa propre résolution MX avec ordonnancement par préférence et sélection d’adresse Happy-Eyeballs (mise en cache par adresse dans pepsi.dns_address), MX implicite via les enregistrements d’adresse (§5.1) et gestion du Null MX (RFC 7505). Le TLS est authentifié par MTA-STS (RFC 8461) avec vérification de l’identité du certificat (RFC 6125) ; les politiques enforce exigent STARTTLS vers un MX listé.

  • pepsi-stage-relay-to-smarthost — relais via un smarthost configuré, choisi par domaine de destinataire (ou un attrape-tout), avec transport par MTA (plain/tls/starttls), vérification de certificat et la suite SMTP AUTH (RFC 4954) complète — PLAIN/LOGIN, CRAM-MD5, SCRAM-SHA-1/-SHA-256 (avec liaison de canal -PLUS facultative), OAUTHBEARER/XOAUTH2, SASL EXTERNAL (certificat client TLS), NTLM, GSSAPI/Kerberos et le DIGEST-MD5 déprécié — ou négociation auto. Voir Authentification du client et l”index des RFC.

Les deux étapes appliquent la protection contre les boucles, en comptant les en-têtes Received: du message par rapport à leur propre option MAX_HOP_COUNT — c’est un réglage d’étape de relais, dans la section [stage-<name>] de chaque étape, et non un réglage de l’ingress, de sorte qu’un message est arrêté au moment où il est sur le point d’être renvoyé plus loin plutôt qu’à son arrivée.

Les deux étapes authentifient le TLS du saut suivant avec DANE (RFC 7672, DANE = off|warn|strict) — en résolvant les enregistrements TLSA du saut et en les comparant à la chaîne présentée, avec priorité sur MTA-STS — et, lorsque [pepsi-tlsrpt] SEND_REPORTS est activé (il est désactivé par défaut), notent chaque session TLS sortante pour le TLS Reporting (RFC 8460) ; pepsi-tlsrpt expédie les rapports agrégés quotidiens. Elles partagent une sémantique de réessai : un échec transitoire met le message en pause avec un backoff exponentiel (RETRY_INITIAL/ RETRY_MAX_INTERVAL/RETRY_FACTOR) jusqu’à MAX_LIFETIME, après quoi il rebondit ou est mis en échec ; un échec permanent achemine vers BOUNCE_STAGE (ou marque la ligne failed).

9.16. Remise locale

Pour les destinataires d’un domaine local, trois étapes classent le courrier sur l’hôte au lieu de le relayer (chacune transfère les destinataires qu’elle ne peut traiter à son NEXT_STAGE, de sorte qu’elles se composent) :

  • pepsi-stage-relay-to-maildir — écrit le message directement dans le Maildir/new/ de chaque utilisateur local. La localité est décidée par LOCAL_DOMAINS et TARGETS (plages d”uid passwd) ; l’écriture privilégiée est faite par le pepsi-helper-maildir-writer setuid-root, atteint via le bit set-group-id pepsi-maildir propre à l’étape.

  • pepsi-stage-relay-to-lmtp — remet le message à un Mail Delivery Agent local (typiquement Dovecot) via LMTP (RFC 2033), qui exécute le filtre Sieve (RFC 5228) de chaque destinataire lors du classement du courrier. Elle remet tous les destinataires en une transaction et achemine plus loin chacun que le MDA rejette selon son état RFC 3463. N’a besoin d’aucun helper privilégié (le MDA abandonne ses privilèges).

  • pepsi-stage-dot-forward — traite le fichier ~/.forward de chaque utilisateur local (le mécanisme sendmail/Postfix classique) via le pepsi-helper-dot-forward setuid-root, qui abandonne ses privilèges au profit de l’utilisateur avant de le lire : les adresses redirigées redémarrent le pipeline, les directives |pipe//file s’exécutent en tant que l’utilisateur, et un destinataire sans ~/.forward passe sans changement.

pepsi-stage-aliases complète celles-ci en étendant les destinataires d’enveloppe à travers une table de style virtual(5) de Postfix (adresse complète ou attrape-tout @domain, étendue transitivement) avant la remise locale.

pepsi-stage-vacation répond au courrier qui arrive pendant l’absence d’un destinataire, et marque le sujet de la copie transmise afin qu’à son retour il voie à quel courrier il a été répondu pour lui. C’est la seule action de filtrage utilisateur que Pepsi met en œuvre lui-même plutôt que de la laisser au Sieve d’un MDA, parce que la réponse est un nouveau message qui doit être signé et relayé — et parce que décider de ne pas en envoyer une relève de la connaissance du pipeline : la RFC 3834 dit de ne jamais répondre à un rebond, au courrier de liste de diffusion, à tout ce qui est déjà marqué automatique, ni à une adresse de service, et Pepsi y ajoute son propre « ne jamais répondre au spam » et une limitation de débit par correspondant. Les dates de congé sont de la configuration par adresse ordinaire, de sorte que la même option est un jour férié national à portée globale et le congé d’une personne dans sa propre ligne pepsi.settings.

pepsi-stage-route les complète différemment : au lieu de remettre, elle choisit un saut suivant par destinataire d’après le domaine de ce destinataire, scindant le message lorsque ses destinataires divergent. C’est ce qui permet à Pepsi de se placer devant un système de courrier existant — les domaines situés derrière la passerelle vont vers ce système, le reste vers l’internet — et c’est là que pepsi-setup prouve qu’aucun domaine servi par cet hôte ne peut être réacheminé vers son propre ingress. Voir Microsoft Exchange comme passerelle.

9.17. Listes de diffusion et archives

Le sous-système de listes de diffusion de Pepsi est une réimplémentation de GNU Mailman 3 : son modèle de données, son architecture rule/chain et handler/pipeline, son API REST, son vocabulaire de commandes par e-mail et les noms de ses modèles de notification relèvent de la conception du projet GNU Mailman, dont la Free Software Foundation détient les droits, et les fichiers repris de GNU Mailman, Postorius ou HyperKitty restent sous licence GNU General Public License (vendor/PEPSI-VENDORING.md les recense). Le test d’acceptation est qu’un mailmanclient, un Postorius ou un HyperKitty non modifié le pilote. Voir Listes de diffusion, Archives et L’API REST de GNU Mailman 3.

  • Cinq étapes de pipeline plutôt qu’un démon dédié : routage, envoi, remise par membre, commandes par e-mail et traitement des rejets – un message de liste est ainsi un enregistrement de file ordinaire que ARC, DKIM, SRS, TLS et les étapes de relais traitent déjà. Comment le sous-système est agencé en donne la carte.

  • Neuf adresses par liste et aucun fichier d’alias. Mailman écrit des tables Postfix ou Exim pour les neuf adresses de chaque liste et recharge le MTA ; Pepsi route sur le destinataire à l’intérieur de son propre pipeline, il n’y a donc rien à régénérer et rien qui puisse se désynchroniser.

  • Une modération au vocabulaire d’amont : dix-huit règles, quatre chaînes terminales (accept, hold, reject, discard) et un pipeline de dix-sept gestionnaires, sous les noms que l’API REST rapporte.

  • En-têtes ``List-*`` (RFC 2369) et ``Archived-At`` (RFC 5064) sur chaque message de liste.

  • Désabonnement en un clic (RFC 8058) – List-Unsubscribe-Post et une URI https: scellée par une clé, honorée par un unique POST. Amont ne l’a pas : c’est le seul endroit où Pepsi va plus loin.

  • Les résumés dans les deux formats : le résumé simple de la RFC 1153 et le multipart/digest MIME, par abonné, avec numérotation de volume et de livraison, un seuil de taille et une minuterie périodique.

  • Le traitement des rejets avec la logique de score d’amont, l’attribution VERP sur chaque copie (les dix-sept détecteurs heuristiques sont donc le recours et non le mécanisme), des messages de sonde facultatifs, des avertissements puis, à terme, le désabonnement.

  • Des archives – une réimplémentation de HyperKitty qui conserve son hachage de Message-ID et son schéma d’URL, de sorte que les liens d’archives existants survivent à une migration – avec recherche plein texte, recherche de sous-chaîne par trigrammes facultative, import et export mbox, et une suppression définitive (purge) qui laisse une pierre tombale.

  • La migration depuis Mailman 2.1 et 3 : la config.pck de 2.1 est lue par une machine pickle restreinte, incapable d’instancier une classe (un pickle non fiable est donc une donnée et non du code), et un serveur Mailman 3 est lu via sa propre API REST.

  • Aucun JavaScript sur aucune page de liste ou d’archives, et aucun 403 sur une surface publique : un refus est le même 404 qu’obtient une liste inexistante, car un 403 révèle qu’elle existe.

9.18. Courrier non sollicité

Pepsi filtre après avoir accepté un message ; tout ce qui suit décide donc du sort d’un courrier déjà dans la file. Trois mécanismes coopèrent au travers d’un unique verdict, state.spam, et d’une unique table, pepsi.whitelist :

  • La liste blanche des correspondants. pepsi-stage-auto-whitelist consigne les destinataires du courrier qu’envoient vos utilisateurs ; pepsi-stage-check-whitelist reconnaît leurs réponses (éventuellement uniquement lorsque DKIM, une signature vérifiée ou un scelleur ARC nommé s’en porte garant) et définit state.spam = false. L’opérateur et les utilisateurs peuvent la gérer à la main avec pepsi-whitelist.

  • Péage à l’envoi (pepsi-stage-anti-spam) : le courrier de toute autre personne est retenu jusqu’à ce que son expéditeur paie un petit montant GNU Taler.

  • Confirmation à l’envoi (pepsi-stage-secretary) : le courrier de toute autre personne est retenu jusqu’à ce que son expéditeur réponde une fois à un défi. La réponse le place en liste blanche, de sorte qu’un correspondant n’est sollicité qu’une seule fois, pour toujours.

Les milters après mise en file (SpamAssassin, rspamd, ClamAV, …) et le filtre de langue peuvent aussi définir ou éclairer le verdict ; voir pepsi-stage-milter.

9.18.1. Listes blanches partagées et par utilisateur

Chacune de ces étapes nomme sa liste avec WHITELIST_NAME. Un nom est soit global (correspondents), soit propre à un compte (alice/sent, l’espace de noms <login>/... ; les modèles {login}/{localpart} se développent en un tel nom pour chaque destinataire, là où une étape les prend en charge).

Une entreprise veut généralement une seule liste partagée par tous les employés : un client à qui l’on a dit d’écrire à un collègue, plutôt qu’à l’employé qui lui a écrit en premier, ne doit pas être arrêté par le filtre anti-spam. L’opérateur la définit une fois

[stage-auto-whitelist]
PROGRAM = pepsi-stage-auto-whitelist
NEXT_STAGE = dkim-sign
WHITELIST_NAME = correspondents

[stage-check-whitelist]
PROGRAM = pepsi-stage-check-whitelist
NEXT_STAGE = anti-spam
WHITELIST_NAME = correspondents

L’opérateur peut nommer n’importe quelle liste, partagée ou par utilisateur, dans chaque couche qu’il contrôle : le fichier INI, pepsi.config_override à n’importe quelle portée (une autre liste partagée pour le domain: d’un service, par exemple) et les enregistrements pepsi.settings écrits avec pepsi-settings. Un titulaire de compte autorisé à modifier pepsi-stage-auto-whitelist ou pepsi-stage-secretary par e-mail (pepsi-stage-edit-settings) ne peut que déplacer le WHITELIST_NAME de ces étapes dans son propre espace de noms, ou le ramener à la liste de l’opérateur : les deux étapes écrivent dans la liste, de sorte qu’un nom partagé ou étranger permettrait à un utilisateur de la remplir d’adresses de son choix. pepsi-stage-check-whitelist ne fait que lire et n’est pas soumise à une telle restriction.

Prudence

Le péage n’exempte que l”expéditeur d’enveloppe nul, il retient donc aussi du courrier pour lequel personne ne paiera. La notification de lien et le courrier de PIN du portail de lien sécurisé portent l’expéditeur d’origine comme expéditeur d’enveloppe ; lorsqu’ils reviennent vers une boîte aux lettres soumise au péage sur le même déploiement, ils sont retenus jusqu’à l’échéance puis rejetés, et le portail cesse de fonctionner sans la moindre erreur. Les messages des listes de diffusion portent l’adresse From: de leurs auteurs, qu’aucune liste de correspondants ne couvre, et leurs champs List-* suppriment la demande de paiement : chaque message est donc retenu puis rejeté sans que personne ait été invité à payer. (Les avis d’absence et les demandes de paiement elles-mêmes utilisent l’expéditeur nul et ne sont pas retenus.)

Mettez ces expéditeurs en liste blanche dans un groupe que nomme [stage-check-whitelist] WHITELIST_NAME : vos propres domaines avec DKIM exigé (la valeur par défaut, qu’un From: falsifié ne peut pas satisfaire), et chaque liste par son identifiant et son scelleur ARC

pepsi-whitelist add correspondents '^[^@]+@example\.com$'
pepsi-whitelist add-list --sealer lists.example.org \
    correspondents users.lists.example.org

pepsi-stage-anti-spam en donne le détail.

9.18.2. Péage à l’envoi et confirmation à l’envoi comparés

Tous deux retiennent le courrier des expéditeurs inconnus et tous deux envoient à l’expéditeur une réponse automatique à expéditeur nul, dans sa langue. Ils diffèrent par ce que l’expéditeur doit prouver :

Péage à l’envoi

Confirmation à l’envoi

L’expéditeur prouve

qu’il dépensera de l’argent pour ce message

qu’il peut lire le courrier à l’adresse depuis laquelle il a écrit

Arrête

le courrier de masse à tout volume, y compris depuis des boîtes aux lettres fonctionnelles

le courrier de masse provenant d’adresses qui ne peuvent pas recevoir (la plus grande partie)

N’arrête pas

un expéditeur disposé à payer

un spammeur disposant d’une boîte aux lettres fonctionnelle, qui peut automatiser la réponse

Dissuasion

économique : chaque message a un coût

juridique uniquement : PENALTY indique ce que l’expéditeur accepte de payer si le courrier n’était pas sollicité, ce que rien ne fait respecter

Nécessite

un backend marchand GNU Taler, et un wallet du côté de l’expéditeur

rien d’autre qu’une réponse

Disons-le clairement : la confirmation à l’envoi est le filtre le plus faible. Elle vaut tout de même d’être utilisée, car elle coûte une réponse à un correspondant légitime et rien à l’exploitation d’un serveur de messagerie — et les deux se combinent. Lorsque les deux sont activés, le secrétaire s’exécute en premier et son UNCHALLENGEABLE_STAGE pointe vers le péage, de sorte que le courrier auquel on ne peut pas demander de répondre (un envoi de liste, un expéditeur non authentifié ou falsifié, un expéditeur au-delà du plafond quotidien de défis) est invité à payer à la place, tandis qu’un expéditeur confirmé atteint le péage avec state.spam = false et le franchit sans y être touché.

La confirmation à l’envoi prend garde au backscatter, puisque son défi va à l’expéditeur d’enveloppe et que l’expéditeur d’enveloppe du spam est généralement falsifié : elle ne défie qu’un expéditeur qui a passé SPF ou DMARC (REQUIRE_AUTHENTICATED) et dont le From: est cette même adresse, au plus MAX_CHALLENGES_PER_SENDER fois par jour, tous utilisateurs confondus ; elle ne répond jamais à ce que la RFC 3834 interdit de répondre ; et son défi ne cite rien du message retenu hormis un court aperçu de son objet (SUBJECT_HINT_LENGTH, huit caractères par défaut).

9.18.3. Filtres anti-spam et secrétaire

Par défaut, les milters de notation du spam (SpamAssassin via spamass-milter, rspamd, MIMEDefang, …) restent avant la vérification de liste blanche, et l’assistant de pepsi-setup génère cet ordre :

… → milter-spamassassin → check-whitelist → secretary → local
                                         UNCHALLENGEABLE_STAGE = local

Un message que le filtre rejette n’atteint jamais le secrétaire, de sorte que le spam évident ne provoque aucun défi vers son expéditeur falsifié — c’est du backscatter évité. Ce qui atteint le secrétaire a passé le filtre, c’est pourquoi l’assistant fixe UNCHALLENGEABLE_STAGE au propre NEXT_STAGE du secrétaire : exécuter à nouveau le filtre reviendrait à analyser deux fois.

L’alternative consiste à placer le filtre après le secrétaire, de sorte qu’il n’analyse que le courrier qui n’a pas pu être soumis à un défi :

… → check-whitelist → secretary → local
                        └─ UNCHALLENGEABLE_STAGE → milter-spamassassin → local

La modification de configuration, pour un filtre en [stage-milter-spamassassin] :

  1. Retirez le filtre de la chaîne principale : définissez le NEXT_STAGE de l’étape qui pointait vers lui au propre NEXT_STAGE du filtre (normalement check-whitelist).

  2. Dans [stage-secretary], définissez UNCHALLENGEABLE_STAGE = milter-spamassassin.

  3. Dans [stage-milter-spamassassin], définissez NEXT_STAGE au NEXT_STAGE du secrétaire (ici local).

Exécutez ensuite pepsi-setup check (ou run) pour valider le graphe.

Le compromis joue dans les deux sens :

  • Pour : le temps CPU du filtre n’est dépensé que pour le courrier qui en a besoin. Le courrier en liste blanche et celui des expéditeurs confirmés — l’essentiel d’une boîte aux lettres personnelle — n’est jamais analysé.

  • Contre : les défis partent désormais sans analyse. Le spam que le filtre aurait rejeté provoque d’abord un défi, soit davantage de backscatter vers des expéditeurs falsifiés (borné par REQUIRE_AUTHENTICATED et le plafond quotidien, mais pas nul) ; et le spam dont l’expéditeur confirme effectivement — le seul type que la confirmation à l’envoi ne peut pas arrêter — est remis sans que le filtre ne l’ait jamais vu.

Quelle que soit la disposition choisie, les milters antivirus et de politique restent sur le chemin principal (avant la vérification de liste blanche) : un correspondant confirmé ou en liste blanche peut tout de même envoyer un logiciel malveillant, depuis un compte compromis ou à son insu, et le verdict d’un filtre de politique s’applique à tout le monde.

9.19. Fonctionnalités opérationnelles

  • Pipeline à table unique avec récupération après plantage : les lignes running orphelines sont réinitialisées au démarrage du dispatcher ; les enfants en vol sont réinitialisés à l’arrêt.

  • Pools de workers élastiques et pipelinés, et chien de garde : chaque étape exécute des processus workers persistants démarrés à la demande jusqu’à son PARALLELISM et récupérés après WORKER_IDLE_TIMEOUT, recyclés après MAX_MESSAGES ; chaque worker reçoit jusqu’à QUEUE_LIMIT messages à la fois (capacité en vol QUEUE_LIMIT × PARALLELISM, sans coût de connexion supplémentaire), et un worker dépassant MAX_RUNTIME sur son message en tête de file est tué (timeout) et remplacé.

  • Outillage de file d’attente : pepsi-queue liste, supprime, remet à une autre étape et débloque en masse les messages.

  • Surveillance de santé : pepsi-status imprime un résumé en lecture seule de la file d’attente, des messages bloqués, des compteurs cumulatifs de remise/échec et des issues TLS sortantes récentes (aussi en --json), adapté à une exécution via SSH.

  • Provisionnement et vérification DNS : pepsi-setup installe le schéma, génère les clés et imprime/valide le DNS (DKIM, SPF, MTA-STS).

  • Serveur HTTP : pepsi-httpd sert, sur un ou plusieurs listeners TLS (sélectionnés par SNI) ou en clair, le fichier de politique MTA-STS (/.well-known/mta-sts.txt), une page Prometheus /metrics (sur un listener ADMIN = yes uniquement — elle décrit le pipeline), les points de terminaison Web Key Directory qui publient les identités propres de ce déploiement (/.well-known/openpgpkey/…, formes directe et avancée), le portail Le portail de repli par lien sécurisé, et le document d”autoconfiguration du courrier ci-dessous.

  • Autoconfiguration des clients de messagerie (draft-ietf-mailmaint-autoconfig) : un client à qui l’on ne donne que fred@example.org récupère https://autoconfig.example.org/mail/config-v1.1.xml et se configure lui-même — hôte de soumission, port, sécurité de transport et authentification, et de même pour le serveur de boîtes aux lettres. La moitié soumission est dérivée du listener de soumission en fonctionnement, de sorte qu’elle ne peut pas dériver du serveur qu’elle décrit ; la moitié boîte aux lettres nomme le MDA auquel le déploiement s’associe. Servi publiquement et sans authentification, comme l’exige le brouillon, parce qu’un client doit le lire avant de pouvoir savoir comment s’authentifier.

  • API et console d’administration : sur un listener explicitement marqué ADMIN = yes — et jamais sur un listener qui les transporterait en clair hors de l’hôte —, le même serveur expose l”L’API d’administration sous /api/v1 (file d’attente, santé, configuration, magasin de clés, journaux d’audit et de courrier) et la La console d’administration de navigateur sous /ui, qui est cliente exactement de cette API. Partout ailleurs, ces chemins répondent le même 404 qu’un chemin inconnu. L’authentification se fait par identifiants de pair sur socket UNIX, par cookie de session ou par jeton porteur, et chaque principal porte des portées ; tout cela est audité.

  • Métriques : pepsi-dispatch note le débit par étape, les kills/timeouts, les plantages et le temps de traitement ainsi que les totaux globaux d’étapes/messages (vidés dans la base de données en une transaction environ une fois par minute), exportés par pepsi-httpd aux côtés des jauges actives/en pause en direct lues depuis la file d’attente.

  • Journalisation structurée via tracing à des niveaux configurables.

Pour les normes que Pepsi n’implémente pas (BINARYMIME entre autres), voir RFC applicables pas encore implémentées.

9.20. Stabilité des fonctionnalités

Chaque fonctionnalité décrite dans ce chapitre est suivie dans une unique table Stabilité des fonctionnalités, qui note — par fonctionnalité — la RFC qui la régit, si elle dispose d’un test automatisé (et de quel type : U unitaire, I intégration, I/U les deux), si elle a été vérifiée à la main, et à quel point elle est largement déployée et exercée selon une télémétrie d’usage anonyme et sur consentement explicite (pepsi-telemetry).

Cette table est générée : contrib/update-feature-stability.sh fusionne le registre maintenu à la main contrib/feature-registry.tsv (colonnes nom, RFC, test et test-manuel) avec un GET /telemetry/report de pepsi-telemetry (les comptes de déploiement et d’usage). Voir Étendre le pipeline pour savoir comment la maintenir à jour quand vous ajoutez une fonctionnalité, écrivez un test ou rafraîchissez les comptes de télémétrie.