64. pepsi-sendmail¶
Lässt Programme auf diesem Host Mail versenden, über die traditionelle Sendmail-Schnittstelle.
64.1. Rolle¶
pepsi-sendmail ist das Programm, das Debian als /usr/sbin/sendmail installiert. Es liest eine Nachricht von der Standardeingabe und übergibt sie über einen lokalen UNIX-Domain-Socket an pepsi-ingress, sodass Software, die von einem MTA die Bereitstellung dieses Pfades erwartet — cron, mail(1), git send-email, logwatch, PHPs mail() —, unverändert über Pepsi versenden kann. Referenz: pepsi-sendmail(1).
Ohne es hat ein Host, auf dem Pepsi läuft, überhaupt keinen lokalen Submission-Pfad: Jene Programme erreichen nur Port 25, wo ein nicht authentifizierter Client kein lokaler Ursprung ist und für jeden Empfänger außerhalb einer bedienten Domain mit 550 5.7.1 Relaying denied abgewiesen wird.
64.2. Wie Debian den sendmail-Pfad zuteilt¶
Debian verwendet dafür nicht update-alternatives. Policy §11.6 lässt jeden Mail Transport Agent Folgendes deklarieren:
Provides: mail-transport-agent
Conflicts: mail-transport-agent
Replaces: mail-transport-agent
sodass genau ein MTA zugleich installiert sein kann und dieser die Pfade schlicht besitzt. Das pepsi-Paket deklariert dieses Tripel und liefert die Schnittstelle als Symlinks auf das vereinheitlichte pepsi-Binärprogramm aus, das anhand von argv[0] verzweigt — dieselbe Technik, die Postfix und exim verwenden:
Pfad |
Verhält sich wie |
|---|---|
|
der Submission-Client |
|
dasselbe (historischer Pfad) |
|
|
|
|
|
der Submission-Client (UUCP-Zustellung) |
Zwei davon sind bewusst keine vollständigen Implementierungen. mailq gibt keine Warteschlange aus — Pepsis Warteschlange ist eine Datenbanktabelle, die ein gewöhnlicher Benutzer nicht lesen darf — und verweist den Aufrufer stattdessen auf pepsi-queue (oder pepsi-status), wobei es mit EX_SOFTWARE endet; newaliases endet mit 0, ohne etwas getan zu haben, weil die Alias-Zuordnung eine Textdatei ist, die pepsi-stage-aliases bei jeder Änderung ihrer mtime neu einliest, und Paketskripte sie bedingungslos aufrufen.
Ein make install aus den Quellen beansprucht diese Pfade bewusst nicht — sie gehören demjenigen MTA, den das System bereits hat, und sie stillschweigend zu übernehmen würde dessen E-Mail-Zustellung ohne Warnung stoppen. Übergeben Sie INSTALL_MTA_LINKS=yes, um es absichtlich zu tun.
64.3. Warum es kein Privileg benötigt¶
Dies unterscheidet sich von jeder anderen sendmail-Implementierung.
Postfix‘ sendmail ist setgid postdrop, damit es in das maildrop-Spool-Verzeichnis schreiben darf. Pepsis Warteschlange ist eine PostgreSQL-Tabelle, die über peer-authentifizierte Rollen erreicht wird, sodass das äquivalente Privileg setuid statt setgid sein müsste — eine sehr viel größere Sache, die man einem Programm überträgt, das von Angreifern beeinflusste Nachrichten-Header parst.
Pepsi umgeht die Frage vollständig, indem es die Identitätsentscheidung auf den Server verlagert. pepsi-ingress liest die Benutzer-ID des sich verbindenden Prozesses vom Kernel (SO_PEERCRED, pro Listener mit AUTH_PEERCRED aktiviert), löst sie zu einem Login auf und schlägt dieses Login in USERNAME_MAP nach — genau so, wie es einen am Submission-Port eintreffenden SASL-Benutzernamen abbildet. Die Submission-Identitätsregeln aus RFC 6409 §6/§8.1 gelten dann unverändert.
pepsi-sendmail ist also ein gewöhnliches unprivilegiertes Programm (und wird wie jedes andere in das gemeinsame pepsi-Binärprogramm eingefaltet), während die Garantien stärker sind, als eine privilegierte Implementierung sie bieten könnte:
Ein Aufrufer kann immer nur als er selbst senden. Der Kernel liefert die Identität; nichts, was der Client sagt, wird als vertrauenswürdig angesehen.
Der Socket darf für alle beschreibbar sein, denn ihn zu öffnen gewährt nichts. Verweigern Sie einem Konto den Zugang, indem Sie es in
USERNAME_MAPauf keine Adresse abbilden, nicht über Rechte.Nichts im Netzwerk gewinnt irgendetwas.
64.4. Im Vergleich zum Vertrauen auf Loopback¶
Die Alternative — 127.0.0.0/8 ::1/128 in das MYNETWORKS des MX-Listeners einzutragen — authentifiziert eine Netzwerkposition statt eines Benutzers:
|
|
|
|---|---|---|
Wer wird authentifiziert |
jeder, der eine TCP-Verbindung öffnen kann |
das konkrete Konto, laut Kernel |
Absenderdurchsetzung |
keine — jeder Prozess darf als beliebiger Absender senden |
|
Vom Netzwerk erreichbar |
nein (nur Loopback), aber der Listener ist ein Netzwerk-Listener |
nein — ein Dateisystem-Socket |
Risiko einer Selbst-Relay-Schleife |
ja: Eine an diesen Host weitergeleitete Nachricht tritt erneut als Submission ein |
nein |
Beide sind verfügbar, aber sie sind keine symmetrischen Wahlmöglichkeiten. Der Socket wird immer bedient, sodass pepsi-setup --wizard nicht danach fragt; die eine verbleibende Frage ist, ob zusätzlich dem Loopback vertraut werden soll, was nur für Software mit Ja zu beantworten lohnt, die SMTP zu 127.0.0.1 spricht und nicht auf den Socket gerichtet werden kann.
64.5. Konfiguration¶
Der Client liest [pepsi-sendmail] SOCKET (Standardwert /run/pepsi/submission.sock) — und der Server ebenso. pepsi-ingress bedient diesen Socket bedingungslos und synthetisiert den Listener aus dieser einen Option, sodass es keinen Listener-Abschnitt zu schreiben gibt und nichts, was mit irgendetwas anderem uneins sein könnte:
[pepsi-sendmail]
SOCKET = /run/pepsi/submission.sock
# ... and that is all. No [pepsi-ingress-listener-*] section.
Von einem Host, auf dem ein MTA läuft, wird erwartet, dass er /usr/sbin/sendmail bereitstellt, also ist der Socket nicht optional. SOCKET = none schaltet ihn für die Fälle ab, die das wirklich wollen (ein Container, eine Appliance), und ihn nicht binden zu können ist fatal statt eingeschränkt: Ein Server, der nur einen Teil dessen bedient, worum er gebeten wurde, ist ein Fehlschlag, kein Teilerfolg.
Unter systemd wird der Deskriptor von pepsi-ingress.socket geerbt, zugeordnet über die Adresse, an die er gebunden ist, statt über einen FD_INDEX. Das zählt, weil ein Index eine Position unter den ListenStream=-Einträgen der Unit ist und sich daher verschiebt, sobald ein Port hinzukommt oder wegfällt; den Kernel zu fragen, woran jeder übergebene Deskriptor gebunden ist, kann mit nichts aus dem Takt geraten. Ohne systemd, oder für ein SOCKET, das keine Unit bindet, bindet pepsi-ingress den Pfad selbst.
Bei der Socket-Aktivierung geht es hier nicht um Privilegien — dies ist kein privilegierter Port —, sondern um das Verzeichnis. /run/pepsi wird mit pepsi-httpd und der Setup-Apply-Türklingel geteilt, und systemd setzt bei jedem Start den Eigentümer eines RuntimeDirectory= auf den User= der deklarierenden Unit, sodass ein unprivilegiertes pepsi-ingress, das dort seinen eigenen Socket anlegt, kaputtgeht, sobald eine andere von ihnen neu startet. Von der .socket-Unit gebunden wird der Knoten von systemd als root angelegt, sodass der Server überhaupt keinen Schreibzugriff auf das Verzeichnis braucht — und der Socket bleibt über einen Dienstneustart hinweg verfügbar, sodass ein gleichzeitiges /usr/sbin/sendmail wartet, statt zu scheitern.
Der synthetisierte Listener ist MODE = plain, SUBMISSION = yes und AUTH_PEERCRED = yes, auf einem Socket mit Modus 0666; nichts davon ist konfigurierbar, weil nichts davon eine Wahl ist. SUBMISSION = yes bringt die RFC-6409-Header-Korrekturen mit sich (ein fehlendes Date: oder Message-ID: wird ergänzt). Ein UNIX-Domain-Submission-Listener ist von der üblichen Regel „Submission erfordert TLS“ ausgenommen: Ein Dateisystem-Socket hat keinen Transport, den ein Angreifer beobachten könnte, und seine Authentifizierung kommt vom Kernel statt von der Leitung, sodass ein Zertifikat nichts schützen würde. Für einen Listener, der tatsächlich mit SERVE = systemd konfiguriert ist, wird diese Ausnahme geprüft, sobald der Deskriptor aufgelöst ist, und nicht beim Parsen — die Familie des Sockets steht in der .socket-Unit —, und pepsi-ingress verweigert den Start, falls es sich um einen Netzwerk-Socket handelt.
Um dem Socket andere Optionen zu geben — eine Gruppe, einen strengeren Modus, keine Peer-Zugangsdaten —, schreiben Sie einen Listener-Abschnitt für denselben Pfad; er ersetzt den eingebauten, statt mit ihm zu kollidieren.
Weil Submissions authentifiziert sind, tragen sie state.local_origin, sodass die pepsi-stage-if-Eingangsstage der Beispiel-Pipeline und der vom Assistenten erstellten Pipelines sie über den ausgehenden Zweig leitet und sie mit der Domain des Autors DKIM-signiert werden.
64.6. Siehe auch¶
pepsi-ingress, pepsi-queue, pepsi-stage-aliases, Erste Schritte auf einem günstigen VPS, pepsi-sendmail(1), pepsi.conf(5).