85.1.10. pepsi-stage-relay-to-internet¶
deliver a message directly to the recipient’s MX hosts
- Section du manuel:
1
85.1.10.1.1. Nom¶
pepsi-stage-relay-to-internet - l’étape de remise directe vers le MX du pipeline Pepsi.
85.1.10.1.2. Synopsis¶
pepsi-stage-relay-to-internet [GLOBAL-OPTIONS] worker
pepsi-stage-relay-to-internet [GLOBAL-OPTIONS] mta-sts DOMAIN
85.1.10.1.3. Description¶
pepsi-stage-relay-to-internet est un programme d’étape exécuté par pepsi-dispatch(1) comme un worker persistant lisant les identifiants de message sur l’entrée standard. Il charge cette ligne pepsi.workqueue (refusant d’agir si son status n’est pas running), lit sa section [stage-<stage>], et remet le message directement aux serveurs MX du domaine destinataire, en utilisant l’expéditeur d’enveloppe propre au message (l’expéditeur nul <> pour les rebonds). La remise se fait à raison d’un destinataire par tentative : une ligne qui contient encore plusieurs destinataires est d’abord scindée en une ligne par destinataire (chacune avec sa tranche de state.dsn), qui toutes rentrent dans cette étape. Un message portant plus de MAX_HOP_COUNT champs d’en-tête Received: est traité comme une boucle de courrier et échoue définitivement.
Il effectue sa propre résolution DNS MX : les hôtes candidats sont essayés par préférence MX croissante (les hôtes à préférence égale dans un ordre aléatoire), un domaine sans enregistrement MX se replie sur ses enregistrements d’adresse (le MX implicite de la RFC 5321 §5.1), et un domaine inexistant ou un null MX RFC 7505 est un échec permanent — le null MX étant signalé comme 556 avec le code d’état étendu 5.1.10 (RFC 7505 §4.1), de sorte que le rebond indique que l’adresse ne pourra jamais être remise au lieu de ressembler à n’importe quel autre 550. Les adresses A/AAAA de chaque hôte MX sont sélectionnées avec Happy-Eyeballs et mises en cache, par adresse, dans la table dns_address ; le réglage ADDRESS_FAMILY (intersecté avec les familles que l’hôte peut router) choisit lesquelles utiliser. Les connexions se font sur le port 25. Chaque requête porte sur le nom pleinement qualifié, de sorte qu’une liste search dans /etc/resolv.conf n’est jamais appliquée : sans cela, un domaine sans enregistrement MX serait résolu une seconde fois sous chaque domaine de recherche, et le NXDOMAIN qui en résulte — et non le NODATA du domaine lui-même — serait signalé, transformant une remise ordinaire par MX implicite en échec permanent.
Sauf désactivation par MTA_STS = no, la politique MTA-STS (RFC 8461) du domaine destinataire est honorée : une politique enforce restreint la remise aux hôtes MX listés via STARTTLS avec un certificat qui valide pour l’hôte, faisant échouer le message (transitoirement) lorsqu’aucun MX ne correspond. Une politique testing est validée de la même manière — la tentative et son issue sont notées pour le TLS Reporting — mais ne bloque jamais la remise : un échec de sécurité du transport (ou une politique qui ne correspond à aucun MX) retombe sur un STARTTLS opportuniste vers tous les hôtes MX, de sorte que le propriétaire du domaine apprend des rapports si enforce casserait son courrier avant d’y basculer. Les domaines sans politique utilisent un STARTTLS opportuniste. Les consultations de politique ne sont fail-open que lorsque rien n’est en cache.
Les politiques sont mises en cache pour leur max_age (plafonné à une semaine). L”id du TXT _mta-sts est revérifié à chaque utilisation d’une politique en cache (RFC 8461 §3.1) : un id modifié déclenche une nouvelle récupération immédiate, tandis qu’un enregistrement transitoirement injoignable (ou l’échec de la récupération d’une politique modifiée) conserve la politique en cache encore valide. Un hôte de politique qui renvoie un 5xx/429 transitoire est traité comme « récupération échouée » (fail open, non mis en cache comme « pas de politique »), tandis qu’un 404 ou un corps qui n’est pas text/plain est une absence définitive.
DANE (RFC 7672) : lorsque DANE vaut warn (la valeur par défaut) ou strict et que l’hôte MX de destination publie des enregistrements TLSA validés par DNSSEC, le certificat du MX est authentifié contre eux plutôt que par PKIX — cela a priorité sur MTA-STS pour cette connexion. DANE repose sur DNSSEC : le résolveur configuré (DNS_SERVERS, ou resolv.conf) doit valider et être atteint par un chemin de confiance, car Pepsi fait confiance au bit AD (Authentic Data) de la réponse plutôt que de valider lui-même. Les réponses TLSA (et MX) sans le bit AD sont traitées comme « pas de DANE ». En mode strict, un enregistrement utilisable mais non concordant (ou une consultation TLSA ou de sécurité MX qui échoue, par exemple pour un enregistrement DNSSEC bogus) diffère le message comme un échec transitoire ; en mode warn, la non-concordance est journalisée et la remise se poursuit. Cette priorité ne vaut que là où DANE est au moins aussi fort que ce qu’il remplace : pour un hôte couvert par une politique MTA-STS enforce, le mode warn n’est pas utilisé (une non-concordance en mode warn accepte n’importe quel certificat, ce qui écarterait l’exigence de validation de la RFC 8461 §5 imposée par la politique) — le certificat est validé par PKIX et la substitution est journalisée. Le mode strict y garde la priorité. Un domaine qui ne publie tout simplement aucun enregistrement TLSA est remis normalement même en mode strict (DANE est opportuniste — il ne mord que lorsque des enregistrements existent). Un domaine qui publie des enregistrements TLSA dont aucun n’est utilisable (un usage PKIX, ou un type de correspondance que ce build n’implémente pas) n’est pas la même chose : d’après la RFC 7672 §2.2, il a tout de même engagé l’hôte à faire du TLS, donc STARTTLS devient obligatoire pour lui — non authentifié, mais jamais en clair — et un saut qui ne le propose pas diffère le message.
SMTP TLS Reporting (RFC 8460) : lorsque [pepsi-tlsrpt] SEND_REPORTS est activé, l’issue de chaque session TLS — un succès, ou un échec classé (starttls-not-supported, validation de certificat/poignée de main ou DANE/MTA-STS) — est notée comme un compteur agrégé dans pepsi.tls_session, indexée par la politique appliquée (tlsa/sts/no-policy-found) et l’hôte MX. pepsi-tlsrpt(1) les compile ensuite en rapports quotidiens. La prise de note est au mieux (un hoquet de base de données ne fait jamais échouer une remise) et est omise pour un message de rapport lui-même (state.tlsrpt).
En cas de succès, la ligne est supprimée (ou avancée si l’étape définit NEXT_STAGE). Un échec transitoire met le message en pause avec un backoff exponentiel (remis en file par pepsi-dispatch(1) lorsque son timeout s’écoule) ; au-delà de MAX_LIFETIME, l’échec est traité comme permanent. Un échec permanent d’un message ordinaire est acheminé vers le BOUNCE_STAGE de l’étape (ou, si aucun n’est configuré, la ligne est marquée failed). Un échec permanent d’un rebond (expéditeur nul) ne rebondit jamais à nouveau : une copie est remise à POSTMASTER s’il est configuré, sinon elle est abandonnée, et la ligne est ensuite supprimée.
DSN (RFC 3461) : lorsque le MX du destinataire annonce l’extension DSN, les paramètres demandés par l’origine (RET/ENVID et les NOTIFY/ORCPT du destinataire, lus dans le state.dsn de la ligne) lui sont propagés ; sinon ils sont omis. En cas d’échec permanent, les NOTIFY/ORCPT du destinataire et l”ENVID sont remis à l’étape de rebond, de sorte qu’un DSN d’échec n’est généré que s’il a été demandé. Cette étape préserve state.dsn.
Lorsque le ORIGINATE_SUCCESS_DSN global de [pepsi] est activé et que le destinataire d’une remise réussie a demandé NOTIFY=SUCCESS, le message est acheminé (après remise) vers BOUNCE_STAGE, qui émet un rapport positif (Action: delivered) à l’expéditeur. Avec l’indicateur désactivé, ou sans demande SUCCESS, une remise réussie est simplement terminée.
Lorsque DELAY_DSN_AFTER est défini et qu’un message encore non remis a passé au moins cette durée en file, un DSN « delayed » (Action: delayed) unique est envoyé à l’expéditeur — mais seulement si l’expéditeur a demandé NOTIFY=DELAY (le délai n’a pas de valeur par défaut implicite). Le message d’origine continue d’être réessayé ; l’avertissement est un message distinct acheminé via BOUNCE_STAGE, et il est envoyé au plus une fois.
Preuve d’origine : lorsque la section partagée [pepsi-origin] est configurée (activée par défaut — pepsi-setup(1) génère un secret si aucun n’est défini), chaque message sortant est estampillé d’un en-tête Pepsi-Origin avant la remise — un HMAC sur le compte expéditeur d’origine, un nonce frais de 128 bits, un horodatage et notre HOSTNAME — et le nonce est noté dans pepsi.origin_nonce (valide pendant deux semaines). Le même estampillage est partagé avec pepsi-stage-relay-to-smarthost(1), de sorte qu’un message emporte une preuve quel que soit le chemin sortant qu’il emprunte. Un rebond de retour intègre cet en-tête, ce qui permet à pepsi-stage-anti-spam(1) de confirmer que le rebond répond à un courrier que nous avons réellement envoyé. L’en-tête est ajouté en tête comme l’en-tête de trace Received: (au-dessus de toute signature existante, de sorte qu’il n’en perturbe aucune) et est laissé non signé. Un nonce qui ne peut être noté (une erreur de base de données) retient le message — il est réessayé comme toute défaillance de l’hôte, voir pepsi-dispatch(1) — car un en-tête dont le nonce n’est pas enregistré ferait paraître falsifié tout rebond de ce message. Sans la section [pepsi-origin], aucun en-tête n’est ajouté ; une section dont le secret ne peut être lu empêche les workers de l’étape de démarrer (la file est retenue, et le journal dit pourquoi), exactement comme pepsi-setup(1) la refuse.
Rétrogradation du contenu (RFC 6152 / RFC 6531) : le message est adapté aux extensions que le MX annonce réellement plutôt qu’envoyé tel quel. Lorsque le corps contient des octets 8 bits et que le MX prend en charge 8BITMIME, l’enveloppe porte BODY=8BITMIME ; lorsqu’il ne le prend pas en charge, l’arbre MIME est parcouru et chaque partie feuille 8 bits est ré-encodée avec un codage de transfert de contenu 7 bits (quoted-printable pour text/*, base64 sinon). Ce qu’une partie déclare n’est pas pris pour preuve de ce qu’elle contient : une partie étiquetée base64 qui transporte des octets 8 bits bruts est ré-encodée elle aussi, et un multipart/* dont le boundary= déclaré n’apparaît nulle part dans son corps n’a aucune partie à préserver et est encodé en entier. Le résultat est ensuite retesté, car le parcours est au mieux sur un message que personne n’a validé (un arbre imbriqué au-delà du plafond de profondeur est recopié tel quel) : un corps portant encore des octets 8 bits est un échec permanent (554) plutôt que quelque chose à mettre sur le fil, ce que la RFC 6152 §3 interdit « en toutes circonstances ».
Lorsque le message a besoin de SMTPUTF8 (UTF-8 dans les en-têtes ou l’enveloppe) et que le MX le prend en charge, l’enveloppe porte SMTPUTF8 ; sinon, les champs d’en-tête UTF-8 sont réécrits en mots encodés RFC 2047 — aux trois endroits où la RFC 2047 §5 en autorise un, de sorte que le champ s’analyse toujours : un corps de champ non structuré, une phrase de nom affiché (une chaîne entre guillemets devient un mot encodé, sans les guillemets) et l’intérieur d’un comment (les parenthèses restent). Un paramètre Content-Type ou Content-Disposition non ASCII, pour lequel la §5 n’a pas de mot encodé, prend à la place la forme étendue de la RFC 2231 (filename*=UTF-8''caf%C3%A9.txt, découpée en sections *0*/*1* lorsqu’une ligne ne suffirait pas à la contenir) ; les paramètres ASCII sont copiés tels qu’écrits. Cela couvre le bloc d’en-têtes de chaque partie MIME, et pas seulement celui du message, puisque c’est là que se trouve le nom de fichier d’une pièce jointe. Un boundary= non ASCII n’a aucune forme encodée — il doit correspondre octet pour octet aux lignes de délimitation du corps, et la RFC 2046 ne lui autorise que des caractères 7 bits —, de sorte que la partie multiple reçoit une nouvelle frontière ASCII aléatoire et que ses lignes de délimitation sont réécrites en conséquence, plutôt que de faire rebondir le message. Un en-tête de partie qui ne peut pas être converti (par exemple des octets Latin-1 bruts) est copié tel quel et laissé au nouveau test 8BITMIME, le transport le voyant comme du contenu de corps. Une adresse d’enveloppe non ASCII, et un octet non ASCII à l’intérieur d’une adresse d’en-tête, ne peuvent pas être rétrogradés — un tel message est un échec permanent (il rebondit) vers un saut sans SMTPUTF8. Ré-encoder un corps casse nécessairement toute signature couvrant le corps déjà présente sur le message (le hachage de corps DKIM de l’originateur et l”ARC-Message-Signature de pepsi-stage-arc(1)) ; c’est inévitable, puisque les capacités du MX ne sont connues qu’après la sélection du MX, et rare en pratique.
85.1.10.1.4. Configuration¶
Le câblage du pipeline et chaque option opérationnelle résident dans la section [stage-<name>] propre à l’étape (PROGRAM = pepsi-stage-relay-to-internet) : les identités SERVER_NAME/POSTMASTER, les timeouts de connexion, le calendrier de backoff des réessais et MAX_LIFETIME, DELAY_DSN_AFTER, MAX_HOP_COUNT, les paramètres DNS, MTA_STS/MTA_STS_TIMEOUT, ADDRESS_FAMILY et DANE. Ils sont documentés dans pepsi.conf(5).
85.1.10.1.5. État¶
Entrées (lues dans le state de la ligne ; toutes facultatives) :
state.dsn— les paramètres RFC 3461 (ret/envidau niveau du message etnotify/orcptdu premier destinataire) ; propagés au MX lorsqu’il annonceDSNet consultés pour conditionner les rapports d’échec/de succès/de délai.state.origin— les indications 8BITMIME/SMTPUTF8 (body_8bit,smtputf8) qu’utilise la décision de rétrogradation du contenu.
Sorties (fusionnées dans le state de la ligne) :
Sur un échec transitoire (
pause) :attempts,last_erroret, une fois qu’un DSN de délai a été émis,delay_sent.Sur un échec permanent avec un
BOUNCE_STAGE(reroute), ou sur un avertissement de délai (un clone mis en file) : un objetstate.bounce(kind=permanentoudelay) pour pepsi-stage-bounce(1). Pour un échec au niveau SMTP, il note aussi le détail structuré du saut suivant —remote_mta(le domaine du destinataire),smtp_code,enhanced_status,phaseetreply_text— de sorte que le rebond puisse énoncer précisément pourquoi la remise a été refusée.Sur une remise réussie avec
ORIGINATE_SUCCESS_DSNetNOTIFY=SUCCESS: un objetstate.bounceaveckind = success.Sur un échec permanent sans
BOUNCE_STAGE(fail) :last_error.
state.dsn et state.origin sont toujours préservés. La disposition de l’état est décrite intégralement dans pepsi.state(7).
Transitions (le calendrier de réessai est RETRY_INITIAL / RETRY_FACTOR / RETRY_MAX_INTERVAL, borné par MAX_LIFETIME) :
remise réussie → terminer (la ligne est supprimée), ou avancer vers NEXT_STAGE s’il en existe un ; avec [pepsi] ORIGINATE_SUCCESS_DSN et un destinataire qui a demandé
NOTIFY=SUCCESS, elle réachemine à la place vers BOUNCE_STAGE pour émettre un DSN positif (Action: delivered) ;échec transitoire → pause pour le prochain intervalle de réessai ;
encore en file au-delà de DELAY_DSN_AFTER avec un destinataire
NOTIFY=DELAY→ mettre en file un clone unique de DSN de délai à BOUNCE_STAGE (l’original reste en file et continue d’être réessayé) ;échec permanent, ou MAX_LIFETIME épuisé → réacheminer vers BOUNCE_STAGE, ou échouer (
failedterminal) si aucun BOUNCE_STAGE n’est configuré ;un rebond non remettable (expéditeur nul) ne rebondit jamais à nouveau : une copie est remise à POSTMASTER (s’il est défini) et la ligne est terminée.
85.1.10.1.6. Commandes¶
- worker
Exécuté comme un worker persistant de pepsi-dispatch(1), lisant les identifiants de message sur l’entrée standard.
- mta-sts DOMAIN
Diagnostic : résoudre et afficher la politique MTA-STS qui s’appliquerait lors d’une remise vers DOMAIN. Utilise le résolveur système et n’a besoin d’aucune configuration de message ni d’étape.
85.1.10.1.7. Options globales¶
- -c FILE, –config FILE
Lit la configuration depuis FILE au lieu de parcourir les emplacements par défaut.
- -L LOGLEVEL, –log LOGLEVEL
Règle la verbosité de journalisation (par défaut
info).- -v, –verbose
Affiche les messages de journal de toutes les sources.
- -h, –help ; -V, –version
Affiche un résumé d’utilisation / la version et quitte.
85.1.10.1.8. Code de sortie¶
L’issue de chaque message est signalée à pepsi-dispatch(1) sur la ligne d’état du worker, et non par le code de sortie.
- 0
Le worker a tourné jusqu’à la fermeture de son entrée standard, ou la consultation mta-sts s’est terminée.
- 1
Une erreur fatale s’est produite (configuration illisible, base de données impossible à ouvrir, ou échec de l’entrée/sortie standard). La raison est écrite dans le journal.
85.1.10.1.9. Exemples¶
Exécuter l’étape sur le message 42 (il doit être running)
echo 42 | pepsi-stage-relay-to-internet -c /etc/pepsi/pepsi.conf worker
Vérifier quelle politique MTA-STS s’applique à un domaine destinataire
pepsi-stage-relay-to-internet -c /etc/pepsi/pepsi.conf mta-sts example.com
85.1.10.1.10. Voir aussi¶
pepsi-config(1), pepsi-stage-bounce(1), pepsi-stage-dkim-sign(1), pepsi-stage-relay-to-smarthost(1), pepsi-dispatch(1), pepsi.conf(5), pepsi.state(7), pepsi-setup(1)
85.1.10.1.11. Bogues¶
Signalez les bogues au gestionnaire de tickets de Pepsi.