64. pepsi-sendmail

Permettre aux programmes de cet hôte d’envoyer du courrier par l’interface Sendmail traditionnelle.

64.1. Rôle

pepsi-sendmail est le programme que Debian installe comme /usr/sbin/sendmail. Il lit un message sur l’entrée standard et le remet à pepsi-ingress par une socket de domaine UNIX locale, de sorte que les logiciels qui attendent d’un MTA qu’il fournisse ce chemin — cron, mail(1), git send-email, logwatch, la fonction mail() de PHP — peuvent envoyer à travers Pepsi sans modification. Référence : pepsi-sendmail(1).

Sans lui, un hôte exécutant Pepsi n’a aucun chemin de soumission locale : ces programmes ne peuvent atteindre que le port 25, où un client non authentifié n’est pas d’origine locale et se voit refusé par 550 5.7.1 Relaying denied pour tout destinataire hors d’un domaine servi.

64.2. Comment Debian arbitre le chemin sendmail

Debian n’utilise pas update-alternatives pour cela. La Charte Debian §11.6 fait déclarer à chaque agent de transport de courrier

Provides:  mail-transport-agent
Conflicts: mail-transport-agent
Replaces:  mail-transport-agent

de sorte qu’un seul MTA à la fois peut être installé et qu’il possède simplement ces chemins. Le paquet pepsi déclare ce triplet et livre l’interface sous forme de liens symboliques vers le binaire unifié pepsi, qui aiguille selon argv[0] — la même technique que celle qu’emploient Postfix et exim :

Chemin

Se comporte comme

/usr/sbin/sendmail

le client de soumission

/usr/lib/sendmail

le même (chemin historique)

/usr/bin/mailq

sendmail -bp

/usr/bin/newaliases

sendmail -bi (sans effet pour Pepsi)

/usr/sbin/rmail

le client de soumission (remise UUCP)

Deux d’entre eux ne sont délibérément pas des implémentations complètes. mailq n’affiche aucune file d’attente — la file d’attente de Pepsi est une table de base de données qu’un utilisateur ordinaire n’a pas le droit de lire — et renvoie plutôt l’appelant vers pepsi-queue (ou pepsi-status), en sortant avec EX_SOFTWARE ; newaliases sort avec 0 sans rien avoir fait, car la table d’alias est un fichier texte que pepsi-stage-aliases relit dès que sa date de modification change, et les scripts de paquet l’exécutent sans condition.

Un make install depuis les sources ne revendique délibérément pas ces chemins — ils appartiennent au MTA que le système possède déjà, et les reprendre en silence arrêterait sa remise de courrier sans le moindre avertissement. Passez INSTALL_MTA_LINKS=yes pour le faire à dessein.

64.3. Pourquoi il n’a besoin d’aucun privilège

Cela diffère de toutes les autres implémentations de sendmail.

Le sendmail de Postfix est setgid postdrop afin de pouvoir écrire dans le répertoire de spool maildrop. La file d’attente de Pepsi est une table PostgreSQL atteinte par des rôles authentifiés par pair, de sorte que le privilège équivalent devrait être setuid plutôt que setgid — bien plus lourd à confier à un programme qui analyse des en-têtes de message sous influence d’un attaquant.

Pepsi évite entièrement la question en déplaçant la décision d’identité vers le serveur. pepsi-ingress lit auprès du noyau l’identifiant d’utilisateur du processus qui se connecte (SO_PEERCRED, activé par listener avec AUTH_PEERCRED), le résout en un login, et recherche ce login dans USERNAME_MAP — exactement comme il mappe un nom d’utilisateur SASL arrivant sur le port de soumission. Les règles d’identité de soumission de la RFC 6409 §6/§8.1 s’appliquent alors inchangées.

pepsi-sendmail est donc un programme ordinaire, non privilégié (et il est plié dans le binaire partagé pepsi comme n’importe quel autre), tandis que les garanties sont plus fortes que ce qu’une implémentation privilégiée pourrait offrir :

  • Un appelant ne peut jamais envoyer qu”en son propre nom. Le noyau fournit l’identité ; rien de ce que dit le client n’est digne de confiance.

  • La socket peut être accessible en écriture à tous, car l’ouvrir n’accorde rien. Refusez un compte en ne le mappant vers aucune adresse dans USERNAME_MAP, pas avec des permissions.

  • Rien sur le réseau n’y gagne quoi que ce soit.

64.4. Comparaison avec la confiance faite au loopback

L’alternative — mettre 127.0.0.0/8 ::1/128 dans le MYNETWORKS du listener MX — authentifie une position réseau au lieu d’un utilisateur :

loopback

sendmail

Qui est authentifié

quiconque peut ouvrir une connexion TCP

le compte précis, d’après le noyau

Contrôle de l’expéditeur

aucun — n’importe quel processus peut envoyer au nom de n’importe qui

USERNAME_MAP, comme pour SASL

Joignable depuis le réseau

non (loopback seulement), mais le listener est un listener réseau

non — une socket de système de fichiers

Risque de boucle d’auto-relais

oui : un message relayé vers cet hôte y revient comme une soumission

non

Les deux sont disponibles, mais ce ne sont pas des choix symétriques. La socket est toujours servie, si bien que pepsi-setup --wizard ne pose pas la question à son sujet ; la seule question qui reste est de savoir s’il faut en plus faire confiance au loopback, ce à quoi il ne vaut la peine de répondre oui que pour un logiciel qui parle SMTP à 127.0.0.1 et qu’on ne peut pas diriger vers la socket.

64.5. Configuration

Le client lit [pepsi-sendmail] SOCKET (par défaut /run/pepsi/submission.sock) — et le serveur aussi. pepsi-ingress sert cette socket inconditionnellement, en synthétisant le listener à partir de cette unique option, de sorte qu’il n’y a aucune section de listener à écrire et rien qui puisse être en désaccord avec quoi que ce soit d’autre :

[pepsi-sendmail]
SOCKET = /run/pepsi/submission.sock

# ... and that is all. No [pepsi-ingress-listener-*] section.

D’un hôte qui fait tourner un MTA, on attend qu’il fournisse /usr/sbin/sendmail : la socket n’est donc pas facultative. SOCKET = none la désactive pour les cas qui le veulent vraiment (un conteneur, une appliance), et échouer à la lier est fatal plutôt que dégradé : un serveur qui ne sert qu”une partie de ce qu’on lui a demandé de servir est un échec, pas un succès partiel.

Sous systemd, le descripteur est hérité de pepsi-ingress.socket, apparié par l’adresse à laquelle il est lié plutôt que par un FD_INDEX. Cela compte parce qu’un index est une position parmi les entrées ListenStream= de l’unité, et se décale donc dès qu’un port est ajouté ou retiré ; demander au noyau à quoi chaque descripteur transmis est lié ne peut se désynchroniser de rien. Hors systemd, ou pour un SOCKET qu’aucune unité ne lie, pepsi-ingress lie le chemin lui-même.

Ici, l’activation par socket n’est pas une affaire de privilège — ce n’est pas un port privilégié — mais de répertoire. /run/pepsi est partagé avec pepsi-httpd et la sonnette de setup-apply, et systemd attribue un RuntimeDirectory= au User= de l’unité qui le déclare à chaque démarrage, de sorte qu’un pepsi-ingress non privilégié qui y crée sa propre socket casse dès qu’une autre de ces unités redémarre. Lié par l’unité .socket, le nœud est créé par systemd en tant que root, si bien que le serveur n’a besoin d’aucun accès en écriture au répertoire — et la socket reste disponible à travers un redémarrage du service, de sorte qu’un /usr/sbin/sendmail concurrent attend au lieu d’échouer.

Le listener synthétisé est MODE = plain, SUBMISSION = yes et AUTH_PEERCRED = yes, sur une socket de mode 0666 ; rien de tout cela n’est configurable, parce que rien de tout cela n’est un choix. SUBMISSION = yes apporte les corrections d’en-têtes de la RFC 6409 (un Date: ou un Message-ID: manquant est fourni). Un listener de soumission en domaine UNIX est exempté de la règle habituelle « la soumission exige TLS » : une socket de système de fichiers n’a aucun transport qu’un attaquant pourrait observer, et son authentification vient du noyau plutôt que du fil, de sorte qu’un certificat ne protégerait rien. Pour un listener qui est configuré avec SERVE = systemd, cette exemption est vérifiée une fois le descripteur résolu plutôt qu’à l’analyse — la famille de la socket vit dans l’unité .socket — et pepsi-ingress refuse de démarrer s’il s’avère que c’est une socket réseau.

Pour donner à la socket d’autres options — un groupe, un mode plus strict, pas d’identifiants de pair —, écrivez une section de listener pour le même chemin ; elle remplace celle intégrée plutôt que d’entrer en collision avec elle.

Comme les soumissions sont authentifiées, elles portent state.local_origin, de sorte que l’étape d’entrée pepsi-stage-if des pipelines d’exemple et de ceux construits par l’assistant les aiguille vers la branche sortante et qu’elles sont signées en DKIM au nom du domaine de l’auteur.

64.6. Voir aussi

pepsi-ingress, pepsi-queue, pepsi-stage-aliases, Démarrage sur un VPS bon marché, pepsi-sendmail(1), pepsi.conf(5).