74. pepsi-stage-list-command¶
Subscribe, unsubscribe and confirm by e-mail; forward `-owner` mail.
74.1. Role¶
pepsi-stage-list-command handles everything a mailing list does by mail that
is not a post: the seven e-mail commands, both subscription workflows, and the
-owner forward. Reference: pepsi-stage-list-command(1).
One sentence governs the whole stage:
The only path that adds an address without that address proving it wants to be there is an explicit pre_verified and pre_confirmed from an authenticated REST client or the CLI.
Nothing in this stage can set either flag. A message is not an authenticated
client, so a join always either asks the named address to confirm or refuses.
This subsystem is a reimplementation of GNU Mailman 3; see Mailing lists, which names what was taken from upstream.
74.2. Addresses¶
Seven, and the first five are not parsed at all: -join/-subscribe become
one synthetic join (the Subject and body are never read, and no results mail
is sent), -leave/-unsubscribe one synthetic leave likewise, and
-confirm+<token> one synthetic confirm whose token is taken from the
address and never from the message. -request is the one whose Subject and
body are parsed. -owner is not a command at all: it is forwarded to every
owner and moderator, and never reaches the parser — which is upstream’s behaviour
and matters, because a message to the owners whose Subject happens to read
unsubscribe must reach a human rather than run a command.
74.3. Commands¶
confirm, echo, end (alias stop), help, join (alias
subscribe), leave (alias unsubscribe) and who. Three of them carry
a security decision rather than a formatting one.
``confirm`` de-duplicates tokens already confirmed in the same message — a mail client that quotes the original produces the token twice — and always stops processing. A token that does not match gets the same answer as one that has expired, because distinguishing them is an oracle for which tokens exist.
``leave`` takes no arguments at all.
join address=costs the named address one confirmation message and subscribes nobody; an equivalentleave address=would let a forgedFrom:unsubscribe a stranger outright. The sender’s own address must additionally be verified.``who`` is gated on
member_roster_visibility, and an unauthorised request is refused rather than answered with an empty list — an empty answer is indistinguishable from a list with no members, which tells a harvester the address is worth trying again.
74.4. Parsing¶
Upstream’s leniency rules, reproduced because twenty years of “send the word HELP
to…” instructions exist. An address command wins outright. The Subject: is
the first command line, RFC 2047-decoded and then forced to ASCII. The body
contributes only the first ``text/plain`` part, and only its first
MAX_COMMAND_LINES lines — a message with no plain part contributes nothing,
because executing commands out of HTML is a place where being helpful is a
security decision. There is no quote, signature or -- handling whatsoever;
every token is lower-cased; blank lines are skipped. An unknown first word is
retried after dropping exactly one word, which is the entire Re:
mechanism and deliberately not a general prefix stripper (Fwd: help me would
otherwise run help). A command that stops ends the run and the rest is
reported as unprocessed. Before any of that, a message carrying
Precedence: bulk|junk|list without X-Ack: yes is discarded silently.
Two of these look like bugs and are not: the retry drops one word and no more,
and the line cap counts lines, not commands — so eleven blank lines followed
by help executes nothing and reports the help as ignored. Raising
MAX_COMMAND_LINES is the fix for “my mail client put a header block in front
of my command”, not quote detection.
74.5. The reply¶
A results mail with upstream’s shape: a preamble, the original message’s details, the results, the unprocessed and ignored lines, and “- Done.”
The original message is never attached. A command message’s From: is
forgeable, so a results mail goes to an address that may not have sent anything;
attaching the original would make this stage a backscatter amplifier.
74.6. Confirmation tokens¶
A token is 256 bits of randomness in base32. It is also the
-confirm+<token> sub-address and the last path segment of the confirmation
URL, so guessing one is completing somebody else’s subscription.
Its lifetime depends on who is being waited for: a token owned by a moderator
lives 180 days, and everything else lives three. A moderator may be on holiday,
and a subscription request that expired in three days would be silently lost
rather than decided. Expired tokens are swept by pepsi-list tasks --once,
which a systemd timer runs; see pepsi-list. Deleting a token discards its
workflow state with it, which is what makes a replayed confirmation find nothing.
74.7. Configuration¶
[stage-<name>]: PROGRAM = pepsi-stage-list-command, RESPONSE_STAGE
(required — the signing tail, not the list delivery stage, which accepts only
a member’s copy from the fan-out and would swallow a notice), NEXT_STAGE
(required — where -owner mail goes once readdressed; never the list router)
and MAX_COMMAND_LINES (default 10). pepsi-setup checks both targets.
74.8. State¶
Inputs:
state.list— the list id, the role the address named, and any-confirmtoken — written by pepsi-stage-list.Outputs: injected reply and notice messages; the command message itself is consumed.
Transitions: finish (a command message is answered, not forwarded), or advance for the
-ownerforward.
74.9. See also¶
Mailing lists, pepsi-stage-list, pepsi-stage-list-post, pepsi-list, pepsi-stage-list-command(1).