70.1.33. pepsi-sendmail¶
submit mail locally through the Sendmail interface
- Manual section:
1
70.1.33.1.1. Name¶
pepsi-sendmail - hand a locally-written message to Pepsi, providing the
traditional /usr/sbin/sendmail interface.
70.1.33.1.2. Synopsis¶
sendmail [-t] [-i] [-f SENDER] [-F NAME] [-v] [-N NOTIFY] [-R RET] [-V ENVID] [RECIPIENT …]
mailq
newaliases
70.1.33.1.3. Description¶
pepsi-sendmail reads a message on standard input and submits it to
pepsi-ingress over a local UNIX-domain socket. It exists so that the software
which expects an MTA to provide /usr/sbin/sendmail — cron, mail(1),
git send-email, logwatch, PHP’s mail() — can send mail through Pepsi
without being reconfigured.
The Debian package installs it under the paths Debian Policy §11.6 requires of a
mail transport agent, each a symlink to the unified pepsi binary, which
dispatches on the name it was invoked under:
Path |
Behaves as |
|---|---|
|
the submission client described here |
|
the same (historical path) |
|
|
|
|
|
the submission client (UUCP delivery) |
A source install does not claim these paths, because they belong to whatever
MTA the system already has; pass INSTALL_MTA_LINKS=yes to make install to
take them over deliberately.
70.1.33.1.4. Authentication¶
pepsi-sendmail holds no privilege and asserts no identity. It is not setuid
or setgid, and it needs no entry in MYNETWORKS.
Instead, pepsi-ingress reads the connecting process’s user id from the kernel
(SO_PEERCRED, enabled per listener with AUTH_PEERCRED), resolves it to a
login through the passwd database, and looks that login up in USERNAME_MAP
exactly as it would a SASL username arriving on the submission port. The RFC 6409
§6/§8.1 rules then apply unchanged: a local program may only use an envelope
sender and From: its own account is mapped to, and an account mapped to no
address is refused outright.
Two consequences are worth stating plainly:
The socket may be world-writable. Being able to open it grants nothing, because it does not determine who you can send as. Deny an account by mapping it to no address in
USERNAME_MAP, not by adjusting permissions.Nothing on the network gains any rights. This is the substantive difference from trusting the loopback interface in
MYNETWORKS, which authenticates a network position rather than a user: under that arrangement any local process can send as anyone, and a message this host relays to itself re-enters as a fresh local submission, which can loop.
Because submissions are authenticated, they are recorded with
state.local_origin set, so the pipeline treats them as this host’s own mail —
DKIM-signing them as the author’s domain and allowing them to relay.
70.1.33.1.5. Options¶
- -t
Read the recipients from the message’s
To:,Cc:andBcc:headers. They are added to any recipients given on the command line (matching Postfix).Bcc:headers are removed from the message before it is sent.- -f SENDER, -r SENDER
Set the envelope sender. Subject to
USERNAME_MAP: the server rejects a sender the invoking account is not entitled to. When omitted, the invoking account’s login is used.- -F NAME
Display name for a
From:header generated when the message has none.- -i, -oi
Do not treat a line containing only a dot as end of input. Accepted for compatibility; input is always read to end-of-file.
- -v
Print the SMTP dialogue on standard error.
- -N NOTIFY, -R RET, -V ENVID
RFC 3461 delivery status notification parameters, passed to the server when it advertises the
DSNextension. See pepsi-stage-bounce(1).- -bm
Read and submit a message. The default.
- -bp
Print the queue. Pepsi’s queue lives in the database rather than in a spool directory, so this reports where to look; use pepsi-queue(1) for the queue itself and pepsi-status(1) for a summary.
- -bi
Rebuild the alias database. Pepsi’s alias map is a plain text file that pepsi-stage-aliases(1) re-reads whenever its modification time changes, so there is nothing to rebuild and this exits successfully without doing anything.
- -bd, -bD, -q…
Refused. Pepsi’s listening server is pepsi-ingress(1) and its queue is drained continuously by pepsi-dispatch(1); accepting these silently would suggest a daemon or a queue run that does not exist.
Unlike every other Pepsi program, pepsi-sendmail takes no -c/--config
option: the sendmail grammar predates GNU option conventions and assigns
-C a conflicting meaning. The configuration file is located the usual way
(see pepsi.conf(5)).
70.1.33.1.6. Configuration¶
[pepsi-sendmail]
- SOCKET
Path of the
pepsi-ingressUNIX-domain submission socket. Default/run/pepsi/submission.sock. pepsi-ingress reads this same option to decide what to serve, so the two ends cannot be pointed at different paths and no listener section is needed.The special value
noneswitches local submission off entirely: the server binds no socket and this program refuses to run.- EHLO_NAME
Name announced in
EHLO. Defaults to[pepsi-ingress] HOSTNAME. Cosmetic: the server authenticates the peer by its credentials, not by this name.- DEFAULT_DOMAIN
Domain appended to a bare, unqualified address — the
rootthat cron and system tools hand tosendmailwithout an@. Defaults to[pepsi-ingress] HOSTNAME, orlocalhostif that is unset.This affects only how the message is addressed. It does not decide who the message is from: that comes from the peer’s uid via
SO_PEERCREDand theUSERNAME_MAP, and setting this option cannot widen it.
The matching server side needs no configuration at all. pepsi-ingress serves
this socket unconditionally, reading the very same SOCKET option to decide
what to bind — so the client and the server cannot be pointed at different
paths, and there is no listener section to write, to keep in step, or to forget.
The listener it synthesises is fixed, because none of it is a choice:
MODE = plain, SUBMISSION = yes (so the RFC 6409 header fixups apply) and
AUTH_PEERCRED = yes, on a socket with mode 0666.
This is a change from earlier versions, where local submission was an optional
[pepsi-ingress-listener-*] section that pepsi-setup --wizard emitted only
if asked. The facility then depended on three separate things agreeing — the
section existing, its UNIXPATH matching SOCKET, and the runtime directory
being writable by the server — and each was individually easy to get wrong, with
the same symptom every time: cron mail silently stops. A host running an MTA is
expected to provide /usr/sbin/sendmail, so it is no longer a question.
Under systemd the descriptor is inherited rather than bound, when the shipped
pepsi-ingress.socket already carries a ListenStream= for the same path.
That is not about privilege — this is not a privileged port — but about the
directory. /run/pepsi is shared with pepsi-httpd and the setup-apply
doorbell, and systemd gives a RuntimeDirectory= to the declaring unit’s own
User=, chowning it on every start; an unprivileged pepsi-ingress creating
its own socket there fails as soon as another of them restarts. Bound by the
.socket unit, the node is created by systemd as root before the service
starts, so the server needs no write access to the directory at all — and the
socket stays available while the service restarts, so a concurrent
/usr/sbin/sendmail waits rather than failing.
The descriptor is matched by the address it is bound to, not by an
FD_INDEX. An index is a position among the unit’s ListenStream= entries,
so it changes whenever a port is added or removed — a second declaration to keep
in step with a file it cannot see. Asking the kernel what each passed descriptor
is bound to has no such failure mode: adding or removing an SMTP port renumbers
nothing, an outbound-only host that drops port 25 needs no edit, and the unit and
the configuration cannot disagree about which socket this is. If no passed
descriptor matches — no systemd, or a SOCKET the unit does not bind —
pepsi-ingress binds the path itself.
If you change SOCKET, change the unit’s ListenStream= to match or remove
it: an entry nothing accepts on is worse than none, because the caller connects
and then blocks until its own read timeout expires (two minutes, then
EX_TEMPFAIL) instead of being refused at once. pepsi-ingress logs a
warning at start-up naming every passed descriptor no listener claimed.
Failure to bind the socket is fatal: pepsi-ingress refuses to start
rather than come up serving only some of what it was asked to serve. A server
that runs with local submission quietly missing is the failure this arrangement
exists to prevent, and it is invisible until mail goes missing.
Writing your own listener section for the same path is still supported, and is how you give the socket different options — a group, a stricter mode, no peer credentials. A section serving that path replaces the built-in listener rather than colliding with it.
70.1.33.1.7. Turning it off¶
SOCKET = none disables local submission entirely: pepsi-ingress serves no
socket, and pepsi-sendmail refuses to run (exit EX_SOFTWARE, before it
reads the message — a permanent misconfiguration, so cron must not retry it).
Programs on the host must then authenticate on 587 or 465 like any other client.
pepsi-setup reports this once, since a mail server with no local submission
path is unusual enough that an operator who did not mean it should hear about it.
70.1.33.1.8. Exit status¶
The traditional sysexits.h values, which cron and other callers act on:
0 |
the message was accepted |
64 |
usage error ( |
65 |
the message was unusable, e.g. |
66 |
the message could not be read from standard input |
70 |
internal error, e.g. the configuration could not be parsed |
75 |
temporary failure — the socket was unreachable or the server
deferred; the caller should retry rather than treat the mail
as lost ( |
77 |
the server refused this sender for the invoking account |
70.1.33.1.9. Examples¶
Send a message, taking the recipients from its headers:
printf 'To: ops@example.org\nSubject: nightly\n\nall good\n' | sendmail -t
Watch the dialogue while diagnosing a refusal:
sendmail -v -f alice@example.org bob@example.net < message.eml
70.1.33.1.10. See also¶
pepsi-ingress(1), pepsi-queue(1), pepsi-status(1), pepsi-stage-aliases(1), pepsi.conf(5)