85.1.25. pepsi-stage-auto-whitelist

whitelist recipients of outgoing mail

Manual section:

1

85.1.25.1.1. Name

pepsi-stage-auto-whitelist - the recipient-recording stage of the Pepsi pipeline.

85.1.25.1.2. Synopsis

pepsi-stage-auto-whitelist [GLOBAL-OPTIONS] worker

85.1.25.1.3. Description

This stage defaults to FUSION = yes: when stage fusion is enabled ([pepsi] ALLOW_FUSION, the default) and this stage is folded into the unified pepsi binary, a predecessor may run it in its own worker process instead of dispatching it separately. See pepsi-dispatch(1) and pepsi.conf(5).

pepsi-stage-auto-whitelist is a stage program run by pepsi-dispatch(1) as a persistent worker reading message ids on standard input. It loads that pepsi.workqueue row (refusing to act unless its status is running) and reads its [stage-<stage>] section. It is the write-side counterpart of pepsi-stage-check-whitelist(1): as an outgoing message passes through, it records every envelope recipient (rcpt_to) in a named whitelist, so that when that person later replies, pepsi-stage-check-whitelist(1) recognises their From: header and lets the reply skip the anti-spam payment gate. Place it on the outbound path (for example, ahead of a relay stage).

The whitelist lives in the pepsi.whitelist table, shared with pepsi-stage-check-whitelist(1) — this stage only INSERTs into it, so it needs no schema of its own. Each recipient address is stored as a whitelist_regex: the address is lower-cased, every non-alphanumeric character is backslash-escaped so it matches literally, and the result is anchored on ^/$ so it matches that address and nothing else. The check stage matches that regex case-insensitively (PostgreSQL’s ~* operator) against the addr-spec parsed out of a reply’s From: header — not the raw header value, so a whitelisted address written into the display-name position (From: "bob@example.com" <evil@attacker.example>) cannot spoof a match. The boundary anchoring is what additionally stops bob@example.com.evil from matching.

Each inserted row carries two condition flags that govern when a future reply counts as whitelisted:

  • dkim_required is set from the stage’s DKIM_REQUIRED option (default yes): when true, the reply is only trusted if its DKIM or ARC signature verified.

  • signature_required is set to true when the outgoing message’s state has encrypted: true (the message was end-to-end encrypted), otherwise false. That key is written by pepsi-stage-encrypt(1), so this stage must run after the encrypt stage (and, since it changes no content, may run between it and pepsi-stage-dkim-sign(1), which is where pepsi-setup(1) --wizard puts it). Placed earlier, no row ever carries the flag. The rows for encrypted correspondents then require their replies to arrive with a verified signature (state.signature_verified, which pepsi-stage-decrypt(1) sets) to match at all.

Every row is written with match_field = 'from': what this stage records is an address a reply will arrive from. It never writes the list-id rows pepsi-stage-check-whitelist(1) also understands — recognising that a user has joined a mailing list is a different inference over different headers, made by hand with pepsi-whitelist add-list.

Existing rows are left untouched (the insert is ON CONFLICT DO NOTHING on the UNIQUE(whitelist_name, match_field, whitelist_regex) constraint), so reprocessing a message is harmless. The stage then advances the message to NEXT_STAGE. It never modifies the message’s own state and never drops, bounces or pauses a message; a message with no recipients simply advances.

85.1.25.1.4. Configuration

Options live in the stage’s own [stage-<name>] section (PROGRAM = pepsi-stage-auto-whitelist): the required WHITELIST_NAME group to populate (one literal whitelist name; the stage expands no {login} or {localpart} placeholder and refuses a value containing one), DKIM_REQUIRED (default yes) and a NEXT_STAGE — also required, since the stage records the recipients and then always hands the message on, so pepsi-setup rejects a section without one. They are documented in pepsi.conf(5).

85.1.25.1.4.1. Whose whitelist

The stage runs on the outbound path, so the per-address layers of the configuration are looked up for the envelope sender: that address’s pepsi.config_override rows at domain:/address: scope and its pepsi.settings row decide which list its recipients are written to.

The operator may name any whitelist, a shared one included, in each of its own layers: the INI file, pepsi.config_override at any scope, and a pepsi.settings row written with pepsi-settings(1). The stage writes to the effective value as it finds it.

An account owner who may edit this stage by mail (it is in pepsi-stage-edit-settings(1)’s EDITABLE_STAGES) may only choose a whitelist of their own: a name in the <login>/... namespace of the account their address resolves to (alice/sent, say), or the whitelist the operator’s configuration already names for them. Anything else – a shared list, another user’s, or any value for an address that is no local account – would let them fill that list with addresses of their choosing, and pepsi-stage-edit-settings(1) refuses it. The login is resolved as pepsi-stage-check-whitelist(1) resolves {login}: the address’s domain must be one of LOCAL_DOMAINS (default [pepsi-ingress] ACCEPTED_DOMAINS), its local part, with the sub-address after RECIPIENT_DELIMITER removed, must be a passwd login, and that account must be permitted by TARGETS. These three options are read from the operator’s stage section, never from the owner’s own override.

A business that wants every correspondent recognised whoever they reply to – a customer told to write to a colleague rather than to the employee who first mailed them – shares one whitelist among all local users, written by this stage and read by the check stage:

[stage-auto-whitelist]
PROGRAM = pepsi-stage-auto-whitelist
NEXT_STAGE = dkim-sign
WHITELIST_NAME = correspondents

[stage-check-whitelist]
PROGRAM = pepsi-stage-check-whitelist
NEXT_STAGE = anti-spam
WHITELIST_NAME = correspondents

85.1.25.1.5. State

Inputs: the envelope recipients come from the rcpt_to column; state.encrypted sets the stored signature_required flag. That key is written by pepsi-stage-encrypt(1), earlier on the same outbound path, when the outgoing message really was encrypted to a recipient key — so a correspondent reached under encryption is later held to the same standard.

Outputs: none on the message itself — the message state is left untouched. The side effect is one pepsi.whitelist row per recipient. The state layout is described in pepsi.state(7).

Transitions: always advances to NEXT_STAGE — after inserting one whitelist row per envelope recipient, or unchanged when there are none. There is no branch; the stage never pauses, fails, reroutes or finishes.

85.1.25.1.6. Commands

worker

Run as a persistent pepsi-dispatch(1) worker, reading message ids on standard input.

85.1.25.1.7. Failures

Recording is fail-open. When the INSERT fails (a database error, a missing grant) the stage logs a warning and advances the message anyway: the whitelist is a convenience for the recipient’s reply, and holding the user’s outbound mail until it can be recorded would be the wrong way round. A pattern that PostgreSQL could not compile is never written (the patterns are escaped addresses, so this is a safeguard, not an expected case).

85.1.25.1.8. Global Options

-c FILE, –config FILE

Read the configuration from FILE instead of searching the default locations.

-L LOGLEVEL, –log LOGLEVEL

Set the logging verbosity (default info).

-v, –verbose

Show log messages from all sources.

-h, –help; -V, –version

Print a usage summary / the version and exit.

85.1.25.1.9. Exit Status

0

The message was processed (recipients recorded and the message advanced).

1

An error occurred (message not found or not running, misconfigured stage — e.g. a missing WHITELIST_NAME — or a database error). The reason is written to the log.

85.1.25.1.10. Examples

Record the recipients of message 42 (it must be running):

echo 42 | pepsi-stage-auto-whitelist -c /etc/pepsi/pepsi.conf worker

An outbound pipeline section that whitelists everyone we mail, requiring their replies to be DKIM/ARC-signed:

[stage-auto-whitelist]
PROGRAM = pepsi-stage-auto-whitelist
NEXT_STAGE = srs
WHITELIST_NAME = trusted-senders
DKIM_REQUIRED = yes

85.1.25.1.11. See Also

pepsi-config(1), pepsi-stage-check-whitelist(1), pepsi-stage-anti-spam(1), pepsi-stage-arc(1), pepsi-dispatch(1), pepsi.conf(5), pepsi.state(7), pepsi-setup(1)

85.1.25.1.12. Bugs

Report bugs to the Pepsi issue tracker.