85.1.22. pepsi-stage-list-command¶
subscribe, unsubscribe and confirm by e-mail
- Manual section:
1
85.1.22.1.1. Name¶
pepsi-stage-list-command - the mailing-list command stage of the Pepsi pipeline.
85.1.22.1.2. Synopsis¶
pepsi-stage-list-command [GLOBAL-OPTIONS] worker
85.1.22.1.3. Description¶
pepsi-stage-list-command is a stage program run by pepsi-dispatch(1). It 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.
The sentence that 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.
85.1.22.1.4. Addresses¶
Seven, and the first five are not parsed at all:
Address |
What happens |
|---|---|
|
one synthetic |
|
never read, and no results mail is sent |
|
one synthetic |
|
|
|
one synthetic |
|
the Subject and the body are parsed |
|
not a command at all: forwarded to every owner and moderator |
-owner mail never reaches the parser, which is upstream’s behaviour and
matters: a message to the owners whose Subject happens to read unsubscribe
must reach a human, not run a command.
85.1.22.1.5. Commands¶
|
Confirm a pending request using its token. |
|
Echo back the arguments, for testing. |
|
Stop processing commands (alias: |
|
Show the command list, or help for one command. |
|
Subscribe (alias: |
|
Unsubscribe (alias: |
|
Show the roster, if you are allowed to see it. |
Three 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.
85.1.22.1.6. Parsing¶
Upstream’s leniency rules, reproduced because twenty years of “send the word HELP to…” instructions exist:
An address command wins outright (see the table above).
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: 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 silently.
An unknown first word is retried after dropping exactly one word. That is the entire
Re:mechanism, and it is deliberately not a general prefix stripper —Fwd: help mewould then runhelp.A command that stops ends the run; the rest is reported as unprocessed.
Before any of that, a message carrying
Precedence: bulk,junkorlistwithoutX-Ack: yesis discarded silently.
Two of these look like bugs and are not. The retry in rule 6 drops one word
and no more. And the cap in rule 3 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.
85.1.22.1.7. 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.
85.1.22.1.8. 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(1). Deleting a token discards its workflow state
with it, which is what makes a replayed confirmation find nothing.
85.1.22.1.9. Configuration¶
- RESPONSE_STAGE (required)
Where replies and confirmation notices are injected. The signing tail, not the list delivery stage: pepsi-stage-list-deliver(1) only accepts a member’s copy from the fan-out and fails anything else, so a notice sent there never goes out. pepsi-setup(1) refuses the wrong value.
- NEXT_STAGE (required)
Where
-ownermail goes once it has been readdressed to the list’s owners. pepsi-setup refuses a value that points back at the router, which would re-route the forward and loop.- MAX_COMMAND_LINES (default 10)
How many lines of the body the parser reads — upstream’s
[mailman] email_commands_max_lines. Counts lines, not commands.
85.1.22.1.10. State¶
Inputs: state.list.id and state.list.role (written by
pepsi-stage-list(1)), and state.list.token for a -confirm
address.
Outputs: none. Everything this stage changes is a database row.
Transitions: finishes (every command path), or advances to
NEXT_STAGE (the -owner forward).
85.1.22.1.11. Commands¶
- worker
Run as a persistent pepsi-dispatch(1) worker, reading message ids on standard input.
85.1.22.1.12. Global Options¶
The usual set — -c/–config, -L/–log, -v/–verbose, -h/–help and -V/–version — behaving as they do for every Pepsi program; see pepsi-config(1).
85.1.22.1.13. Errors and retries¶
A message that names a list which no longer exists is reported to the sender through the stage’s BOUNCE_STAGE (one DSN per recipient, saying the mailing list no longer exists), or failed when the stage has none; a null-sender message is dropped instead. A message the MIME parser refuses is failed at once. Any other error is a fault of the host and is retried with back-off until MAX_LIFETIME (see pepsi-dispatch(1)).
The grace-period claim for the results mail is recorded before
list_command_commit commits the commands and the mail, so it is keyed on the
message’s queue token: a message retried after that commit failed is allowed to
send its results again rather than being told it was already answered today.
85.1.22.1.14. Exit Status¶
- 0
The message was handled.
- 1
The worker could not run (its database could not be opened, or standard input/output failed). The reason is written to the log.
85.1.22.1.15. Examples¶
[stage-list-command]
PROGRAM = pepsi-stage-list-command
RESPONSE_STAGE = dkim-sign
NEXT_STAGE = local
MAX_COMMAND_LINES = 10
85.1.22.1.16. See Also¶
pepsi-list(1), pepsi-stage-list(1), pepsi-stage-list-post(1), pepsi-dispatch(1), pepsi.conf(5), pepsi.state(7), pepsi-setup(1)
85.1.22.1.17. Bugs¶
Report bugs to the Pepsi issue tracker.