85.1.11. pepsi-stage-relay-to-smarthost¶
relay a message through an upstream smarthost
- Section du manuel:
1
85.1.11.1.1. Nom¶
pepsi-stage-relay-to-smarthost - l’étape de remise vers smarthost du pipeline Pepsi.
85.1.11.1.2. Synopsis¶
pepsi-stage-relay-to-smarthost [GLOBAL-OPTIONS] worker
85.1.11.1.3. Description¶
pepsi-stage-relay-to-smarthost 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 sauf si son status est running), lit sa section [stage-<stage>], et relaie le message à un MTA amont configuré (smarthost). Comme dans pepsi-stage-relay-to-internet(1), une ligne portant plusieurs destinataires est d’abord scindée en une ligne par destinataire, et un message portant plus de MAX_HOP_COUNT champs d’en-tête Received: échoue définitivement comme boucle de courrier.
Le domaine du destinataire sélectionne le smarthost : la section [pepsi-stage-relay-to-smarthost-mta-<name>] dont le DOMAINS le liste, ou le MTA attrape-tout (CATCH_ALL = yes). Un domaine ne peut être listé que par un seul MTA, et au plus un MTA peut être l’attrape-tout. Ces sections MTA sont partagées entre toutes les étapes de sortie (voir pepsi.conf(5)). Le message est envoyé avec son propre expéditeur d’enveloppe, en utilisant le transport configuré du MTA (MODE = plain/tls/starttls, par défaut starttls), la vérification de certificat et l’authentification.
En cas de succès, la ligne est supprimée (ou, si l’étape règle NEXT_STAGE, avancée). 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 — y compris un destinataire sans MTA correspondant — est acheminé vers le BOUNCE_STAGE de l’étape (ou, si aucun, la ligne est marquée failed). Un échec permanent d’un rebond (expéditeur nul) est abandonné plutôt que renvoyé en rebond. pepsi-setup(1) refuse une étape sans aucune section [pepsi-stage-relay-to-smarthost-mta-*] (elle renverrait chaque message en rebond), et avertit lorsqu’aucun MTA ne règle CATCH_ALL, en nommant les domaines qui sont acheminés.
Contrairement à pepsi-stage-relay-to-internet(1), pepsi-stage-relay-to-smarthost ne fait aucune découverte MX et n’utilise jamais de MTA-STS appris par DNS : il ne parle qu’aux smarthosts nommés dans la configuration.
DSN (RFC 3461) : lorsque le smarthost annonce l’extension DSN, les paramètres que l’origine a demandés (RET/ENVID sur MAIL FROM et les NOTIFY/ORCPT du destinataire sur RCPT TO, lus depuis le state.dsn de la ligne) lui sont propagés ; lorsqu’il n’annonce pas DSN, 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 lorsque l’expéditeur en a demandé un. 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.
Rétrogradation de contenu (RFC 6152 / RFC 6531) : le message est adapté aux extensions que le smarthost annonce réellement. Lorsque le corps est 8 bits et que le smarthost prend en charge 8BITMIME, l’enveloppe porte BODY=8BITMIME ; lorsqu’il ne le prend pas en charge, chaque partie MIME terminale 8 bits est ré-encodée avec un codage de transfert de contenu 7 bits (quoted-printable pour text/*, base64 sinon) — d’après les octets constatés, et non d’après l’encodage déclaré — puis le résultat est retesté, un corps portant encore des octets 8 bits étant un échec permanent (554) plutôt qu’un message envoyé en violation de la RFC 6152 §3. Lorsque le message a besoin de SMTPUTF8 et que le smarthost le prend en charge, l’enveloppe porte SMTPUTF8 ; lorsqu’il ne le prend pas en charge, les champs d’en-tête UTF-8 sont réécrits en mots encodés RFC 2047 partout où le §5 en permet un, les paramètres Content-Type et Content-Disposition non ASCII de toute partie MIME prennent la forme RFC 2231 (filename*=UTF-8''…), un boundary= non ASCII est remplacé par une nouvelle frontière ASCII dans toute sa partie multiple, et un message portant une adresse non ASCII rebondit. Ré-encoder un corps casse toute signature couvrant le corps déjà présente sur le message (le condensat de corps DKIM de l’expéditeur d’origine et l”ARC-Message-Signature) ; voir pepsi-stage-relay-to-internet(1).
Preuve d’origine : lorsque la section partagée [pepsi-origin] est configurée (activée par défaut), cette étape estampille chaque message sortant d’un en-tête Pepsi-Origin et note son nonce, exactement comme le fait pepsi-stage-relay-to-internet(1) (le smarthost transmet l’en-tête au MX final), de sorte qu’un rebond revenant pour du courrier relayé par smarthost puisse être vérifié par pepsi-stage-anti-spam(1). Voir cette étape et pepsi.conf(5) pour les détails. Sans la section, aucun en-tête n’est ajouté. Comme là-bas, un nonce qui ne peut être noté retient le message pour un réessai.
La configuration lue par une remise — cette section, [pepsi-tlsrpt], [pepsi-origin] et [pepsi] — est vérifiée au démarrage d’un worker ; une section qui ne s’analyse pas empêche les workers de démarrer (la file est retenue, et le journal dit pourquoi) au lieu de mettre chaque message en échec. Rien n’est lu dans la configuration après que le smarthost a accepté un message, de sorte qu’un message accepté n’est jamais réessayé à cause d’une erreur de configuration.
SMTP TLS Reporting (RFC 8460) : lorsque [pepsi-tlsrpt] SEND_REPORTS est réglé, chaque session TLS vers le smarthost est notée (au mieux) comme un compteur agrégé dans pepsi.tls_session — un succès, ou un échec TLS classifié — indexée par la politique appliquée (tlsa lorsque DANE a authentifié le smarthost, sinon no-policy-found) et l’hôte smarthost. pepsi-tlsrpt(1) les compile en rapports quotidiens ; ce relevé est ignoré pour un message de rapport lui-même (state.tlsrpt).
85.1.11.1.4. Configuration¶
Le câblage du pipeline et les options opérationnelles (SERVER_NAME, les timeouts de connexion, le calendrier de backoff de réessai et MAX_LIFETIME, DELAY_DSN_AFTER, MAX_HOP_COUNT, ADDRESS_FAMILY et les paramètres DNS de DANE) résident dans la section [stage-<name>] propre à l’étape (PROGRAM = pepsi-stage-relay-to-smarthost) ; les smarthosts amont sont définis une seule fois dans les sections partagées [pepsi-stage-relay-to-smarthost-mta-<name>] (HOST et PORT, tous deux obligatoires, MODE, TLS_VERIFY/TLS_CA/DANE, les identifiants AUTH, HELO_NAME, son propre ADDRESS_FAMILY — dont la valeur par défaut est celle de l’étape — et DOMAINS/CATCH_ALL, dont au moins un est obligatoire). Tous sont documentés dans pepsi.conf(5).
85.1.11.1.5. Authentification¶
Lors d’un relais via un smarthost, Pepsi prouve sa propre identité à ce MTA amont via SMTP AUTH (RFC 4954), configuré par smarthost avec l’option AUTH de [pepsi-stage-relay-to-smarthost-mta-<name>]. Les mécanismes pris en charge sont :
none(par défaut) — pas d’authentification client.plain— SASLPLAIN(RFC 4616) : lesUSERNAME/PASSWORDsont envoyés encodés en base64.login— SASLLOGIN: l’échange utilisateur/mot de passe en deux étapes, hérité, qu’utilisent certains smarthosts plus anciens.cram-md5— SASLCRAM-MD5(RFC 2195) : un défi/réponse dans lequel Pepsi prouve la connaissance duPASSWORDviaHMAC-MD5sur le défi du serveur, de sorte que le mot de passe lui-même n’est jamais envoyé. Plus faible que SCRAM (pas de salage, pas d’authentification mutuelle, MD5) — préférezscram-*lorsque disponible.digest-md5— SASLDIGEST-MD5(RFC 2831) : un défi/réponse salé qui authentifie aussi le serveur auprès de Pepsi viarspauth(une non-concordance interrompt l’échange). Déprécié — la RFC 6331 a passéDIGEST-MD5au statut Historic ; il n’est fourni que pour les smarthosts hérités qui n’offrent rien de mieux, et Pepsi émet un avertissement (à la fois depuis pepsi-setup(1) au moment de la configuration et dans les journaux de relais la première fois qu’il est utilisé). Il n’est jamais sélectionné parauto. Préférezscram-*oucram-md5.scram-sha-1/scram-sha-256— SASLSCRAM-SHA-1/SCRAM-SHA-256(RFC 5802 / RFC 7677) : un défi/réponse salé qui, contrairement àcram-md5, authentifie aussi le serveur auprès de Pepsi — l’échange est interrompu si la signature du serveur ne se vérifie pas au regard duPASSWORD, ou si le serveur accepte la connexion sans renvoyer du tout sa signatureserver-final(de sorte qu’un pair ne peut esquiver l’authentification mutuelle en répondant235prématurément).SCRAM-SHA-256est préféré.USERNAME/PASSWORDsont normalisés avec SASLprep (RFC 4013).scram-sha-1-plus/scram-sha-256-plus— les variantes à liaison de canal-PLUSdes précédents, liant l’échange SASL au canal TLS sous-jacent avectls-server-end-point(RFC 5929 ; un condensat du certificat du serveur). Cela détecte un homme du milieu qui termine le TLS et le rétablit. Nécessite unMODEchiffrant (il n’y a sinon aucun canal à lier), et le smarthost doit annoncer le mécanisme-PLUS.auto— négocier le mécanisme à mot de passe le plus fort que le smarthost annonce dans sonEHLO, à partir des mêmesUSERNAME/PASSWORD. Ordre de préférence :SCRAM-SHA-256-PLUS>SCRAM-SHA-256>SCRAM-SHA-1-PLUS>SCRAM-SHA-1>CRAM-MD5>LOGIN>PLAIN(les variantes-PLUSne sont choisies que sur une session TLS). Le réglage recommandé lorsque vous n’avez pas besoin d’épingler un mécanisme spécifique.external— SASLEXTERNAL(RFC 4422 annexe A) : le smarthost authentifie Pepsi par le certificat client TLS qu’il présente pendant la poignée de main (TLS mutuel), de sorte qu’aucun mot de passe n’est envoyé. Nécessite queTLS_CLIENT_CERT/TLS_CLIENT_KEYsoient configurés (voir TLS mutuel ci-dessous). UnUSERNAMEfacultatif est envoyé comme identité d’autorisation SASL ; lorsqu’il est omis, le smarthost dérive l’identité du certificat.oauth— jeton porteur SASL OAuth 2.0, requis par les grands fournisseurs tels que Gmail et Microsoft 365. Le mécanisme concret est choisi automatiquement à partir de l”EHLOdu smarthost, préférant l”OAUTHBEARER(RFC 7628) standardisé et retombant sur leXOAUTH2de facto.USERNAMEest l’adresse de compte envoyée dans l’échange SASL etTOKEN_FILEest le chemin d’un fichier contenant le jeton d’accès OAuth 2.0 en cours (les espaces de début/fin sont retirés). Le fichier est relu à chaque remise, de sorte qu’un rafraîchisseur externe peut remplacer un jeton expiré sans redémarrer Pepsi.Les jetons d’accès sont à courte durée de vie (typiquement environ une heure). L’étape de relais ne fait que lire le jeton ; garder
TOKEN_FILEà jour est un travail externe. Pepsi livre pepsi-helper-token-refresh(1) pour cela — un service qui obtient de nouveaux jetons depuis le point de terminaison de jeton du fournisseur et réécrit le fichier — mais il est facultatif et ne fait pas partie depepsi.target; un site peut utiliser tout équivalent (une tâche cron, un agent cloud). Une remise tentée alors que le jeton est manquant, vide, expiré ou autrement rejeté est traitée comme un échec transitoire, de sorte que le message reste en file d’attente et est réessayé une fois un nouveau jeton en place, au lieu de rebondir.Pour que le worker de relais (exécuté en tant que
pepsi) puisse lire un jeton écrit par le rafraîchisseur, le binaire de l’étape est installé SGID au groupepepsi-token(mode2550, propriétairepepsi:pepsi-token, de sorte que seuls le compte du dispatcher et root peuvent l’exécuter) et les fichiers de jeton lui sont lisibles par le groupe (mode0640dans le répertoire SGID/var/pepsi/tokens). Voir le chapitre Étendre le pipeline du manuel pour le contrat de rafraîchissement complet et comment substituer votre propre rafraîchisseur.ntlm— le mécanisme MicrosoftNTLMde facto (sans RFC), pour les déploiements hybrides Exchange / Office 365 plus anciens. Seul NTLMv2 est implémenté (NTLMv1 est cryptographiquement cassé et jamais envoyé). Comme Pepsi n’exécute jamais NTLM que sur TLS, la réponse porte toujours l’Extended Protection for Authentication (EPA) : les paires AVMsvAvChannelBindings(tls-server-end-point, RFC 5929) etMsvAvTargetName(le SPNsmtp/<host>) plus le code d’intégrité de message, de sorte qu’elle s’authentifie auprès des serveurs imposant l’EPA. LeNT_DOMAINrequis est le domaine Windows et leNT_WORKSTATIONfacultatif est un nom de client cosmétique ;USERNAME/PASSWORDsont les identifiants du compte. NTLM est faible (il n’utilise que le condensat NT) et n’authentifie pas le serveur auprès de Pepsi — préférezgssapi,scram-*ouoauthlà où les smarthosts les prennent en charge.pepsi-setupavertit lorsqu’il est configuré.gssapi— SASLGSSAPI/ Kerberos (RFC 4752), pour les smarthosts kerbérisés (typiquement un Exchange sur site dans un realm Active Directory). L’authentification utilise un ticket de service Kerberos pourSERVICE_NAME(un nom de service basé sur l’hôte, par défautsmtp@<HOST>) obtenu d’un cache d’identifiants Kerberos ; après l’échange de jetons GSS, la couche de sécurité SASL est négociée à aucune couche de sécurité (la session est déjà protégée par TLS). Aucun mot de passe n’est configuré.Pepsi ne fait que lire le cache d’identifiants et ne détient jamais la clé Kerberos à long terme : un processus externe — un keytab plus
k5start/cron— doit garder un ticket-granting ticket à jour. Le cache est leKRB5CCNAMEambiant (réglé dans l’environnement du service dispatcher) sauf si l’optionKRB5CCNAMEpar MTA le redéfinit. Pour que le worker de relais (exécuté en tant quepepsi) puisse lire un cache écrit par le rafraîchisseur, placez un cacheFILE:sous le répertoire SGID/var/pepsi/krb5(groupepepsi-token, pour lequel le binaire de l’étape s’exécute déjà SGID pour OAuth/mTLS). Une remise tentée alors que le cache est manquant ou que le ticket a expiré est un échec transitoire (le message reste en file d’attente, commeoauth). Voir le chapitre Étendre le pipeline du manuel pour le contrat de rafraîchissement.
Tous les mécanismes à mot de passe (plain, login, cram-md5, digest-md5, la famille scram-*, auto et ntlm) ainsi que oauth et gssapi envoient ou dérivent un identifiant (ou un contexte de sécurité), de sorte que Pepsi ne les offre qu’une fois la session chiffrée (utilisez MODE = tls ou starttls) ; chacun est refusé si le smarthost n’annonce pas le mécanisme AUTH correspondant. Les mécanismes à défi-réponse (cram-md5, digest-md5, scram-*, ntlm) ne transmettent jamais le mot de passe lui-même, mais Pepsi exige tout de même TLS pour eux. external et gssapi n’envoient aucun mot de passe sur SMTP — l’identifiant est le certificat client TLS ou un ticket Kerberos — mais nécessitent de même une session chiffrée. Pour ntlm et gssapi, un smarthost MODE = plain est rejeté au moment de la configuration.
Un smarthost qui rejette l’identifiant (535/534/504), tout comme une vérification du serveur échouée avec digest-md5 ou scram-*, est un échec transitoire : le message reste en file d’attente et est réessayé, exactement comme pour un ticket Kerberos manquant. Ce qui est fautif dans ce cas est la configuration locale — un mot de passe renouvelé, un compte verrouillé, un mécanisme que le smarthost n’implémente pas — de sorte que faire rebondir renverrait toute la file d’attente sortante aux propres utilisateurs du site alors que l’opérateur est encore en mesure de corriger le problème. Surveillez le journal (ou pepsi-status(1)) pour repérer les échecs AUTH répétés ; pepsi-setup --wizard vérifie un identifiant auprès du smarthost au moment où il est saisi.
85.1.11.1.6. TLS mutuel¶
Indépendamment de (ou en plus de) AUTH, un smarthost peut être configuré pour présenter un certificat client pendant la poignée de main TLS (TLS mutuel) :
TLS_CLIENT_CERT— chemin de la chaîne de certificats PEM à présenter.TLS_CLIENT_KEY— chemin de la clé privée PEM correspondante.
Les deux doivent être réglés ensemble, et seulement avec un MODE chiffrant (tls ou starttls). Le certificat est présenté à chaque connexion et est chargé à neuf à chaque fois, de sorte qu’un certificat renouvelé est pris en compte sans redémarrer Pepsi. Il s’applique aux sessions PKIX, TLS_VERIFY = no et DANE indifféremment (le certificat client est orthogonal à la manière dont le certificat du serveur est validé).
Le certificat seul satisfait de nombreux smarthosts (TLS mutuel au niveau du transport, avec AUTH = none). Régler AUTH = external émet en plus un AUTH EXTERNAL SMTP de sorte que le smarthost lie la session SMTP au certificat présenté ; il peut aussi être combiné avec AUTH = plain/oauth si un smarthost veut à la fois un certificat client et un mot de passe/jeton.
La clé privée est du matériel secret. Comme pour les fichiers de jeton OAuth, le worker de relais (exécuté en tant que pepsi) la lit via le groupe SGID pepsi-token : sur une installation Debian, déposez le certificat et la clé dans /var/pepsi/tls (créé SGID pepsi-token), de sorte que la clé hérite du groupe pepsi-token et soit lisible par l’étape. Gardez la clé en mode 0640 (ou plus strict) et jamais lisible par tous.
85.1.11.1.7. État¶
Entrées (lues dans le state de la ligne ; toutes facultatives) :
state.dsn— les paramètres RFC 3461 ; propagés au smarthost lorsqu’il annonceDSNet consultés pour conditionner les rapports d’échec/succès/délai.state.origin— les indications 8BITMIME/SMTPUTF8 utilisées par la rétrogradation de 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 un échec au niveau SMTP, il note aussi le détail structuré du saut suivant —remote_mta(le smarthost),smtp_code,enhanced_status,phaseetreply_text— de sorte que le rebond puisse énoncer précisément pourquoi le smarthost a refusé le message.Sur un relais réussi 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) :
relais réussi → terminer (la ligne est supprimée), ou avancer vers NEXT_STAGE s’il en est réglé 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 d’attente au-delà de DELAY_DSN_AFTER avec un destinataire
NOTIFY=DELAY→ mettre en file un clone de DSN de délai à usage unique à BOUNCE_STAGE (l’original reste en file d’attente) ;échec permanent (y compris un destinataire sans MTA correspondant), ou MAX_LIFETIME épuisé → réacheminer vers BOUNCE_STAGE, ou échouer (
failedterminal) si aucun BOUNCE_STAGE n’est configuré ;un rebond impossible à remettre (expéditeur nul) n’est jamais renvoyé en rebond — il est simplement abandonné (cette étape n’a pas de copie de double rebond vers le postmaster).
85.1.11.1.8. Commandes¶
- worker
Exécuté comme un worker persistant de pepsi-dispatch(1), lisant les identifiants de message sur l’entrée standard.
85.1.11.1.9. 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.11.1.10. 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.
- 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.11.1.11. Exemples¶
Exécuter l’étape de sortie sur le message 42 (il doit être running)
echo 42 | pepsi-stage-relay-to-smarthost -c /etc/pepsi/pepsi.conf worker
85.1.11.1.12. Voir aussi¶
pepsi-config(1), pepsi-stage-bounce(1), pepsi-stage-dkim-sign(1), pepsi-stage-relay-to-internet(1), pepsi-dispatch(1), pepsi.conf(5), pepsi.state(7), pepsi-setup(1)
85.1.11.1.13. Bogues¶
Signalez les bogues au gestionnaire de tickets de Pepsi.