85.1.57. pepsi-helper-mailbox-scan¶
list the addresses a mailbox’s owner has written to
- Manual section:
1
85.1.57.1.1. Name¶
pepsi-helper-mailbox-scan - extract correspondent addresses from a mailbox.
85.1.57.1.2. Synopsis¶
pepsi-helper-mailbox-scan (–local-domain DOMAIN | –own-address ADDRESS)… [OPTIONS] PATH…
pepsi-helper-mailbox-scan (–local-domain DOMAIN | –own-address ADDRESS)… –imap URL –imap-password-stdin
pepsi-helper-mailbox-scan (–local-domain DOMAIN | –own-address ADDRESS)… –doveadm-stdin
85.1.57.1.3. Description¶
pepsi-helper-mailbox-scan reads a mailbox and prints, one per line, the
addresses that its owner has sent mail to. It is the scanning half of
pepsi-whitelist(1)’s import subcommand, which spawns it and turns its
output into pepsi.whitelist rows; it can also be run by hand, since its
output is a trivially parseable tagged line protocol (see Output below) that
is readable as it stands.
A message counts as “sent by us” when both of the following hold.
Its From: — or Sender:, for mail submitted on somebody’s behalf — is at one
of the –local-domain domains; or, when –own-address is given, it is
exactly one of those addresses. (The two are alternatives, not a union: naming any
own-address switches the sender test over to that list entirely, and the domains
are then used only to decide which recipients are dropped as local.) And its
header block carries no Received:, Return-Path:, Delivered-To: or
X-Original-To: field.
The second condition is what makes the first one mean anything. From: is
unauthenticated: anybody may put your address in it, and such a message sits in
your INBOX looking exactly like mail you sent, so scanning an inbox on the
From: test alone lets a stranger choose the addresses that end up in your
whitelist. Those four fields are written by a receiving MTA or MDA and never by a
mail user agent composing a message, so their presence proves the message
arrived here whatever its From: says. What is left is the copy your client
filed after composing it, which is what a scan is meant to read. Where that copy
lives does not matter: a Maildir scan walks the whole tree, so ~/Maildir
covers .Sent and every archive folder. A plain /var/mail/ spool, holding
only received mail, yields nothing.
The To:, Cc:
and Bcc: addresses of such messages are the correspondents; addresses at a
local domain are omitted, because a local sender never passes through the inbound
whitelist gate. Addresses are deduplicated, lower-cased and syntax-checked, and
each is printed at most once.
Only the header block of each message is read: every reader stops at the blank line that ends the headers, and message bodies are never touched. Both the block and any single line of it are capped at –max-header-bytes, so one absurd message cannot make the scan allocate without bound.
Two things do grow with the work rather than being constant. The set of distinct correspondent addresses is held in memory (that is what deduplication means), so it is proportional to the number of people written to — not to the size of the mailbox. And –doveadm-stdin is the one input that may buffer its whole source: Dovecot may emit its result as a single top-level JSON array, which is parsed as one value.
85.1.57.1.3.1. Why this is a separate, unprivileged program¶
This is the only standalone pepsi-helper-* program that carries no
setuid or setgid bit (mode 0755). The other three standalone ones —
pepsi-helper-maildir-writer(1), pepsi-helper-dot-forward(1) and
pepsi-helper-auto-pay(1) — are installed setuid-root and group-restricted
(mode 4750). (pepsi-helper-token-refresh(1) holds no privilege either, but
it is folded into the unified pepsi binary rather than installed as a
program of its own.)
The reason is the direction of privilege. pepsi-whitelist(1) is setuid
because it needs a database identity, and a setuid process keeps its owner in the
saved set-user-id and can regain it at any point. Parsing a mailbox is the opposite
kind of work: it processes megabytes of attacker-controlled RFC 5322 — every
message anybody ever sent the user — and it needs no privilege beyond the user’s
own. So the parsing runs here, in a process pepsi-whitelist spawns with
setresuid setting the real, effective and saved ids to the target user. With
no privileged id left anywhere in its credentials, this program cannot become the
whitelist account, cannot reach the database, and can read nothing the user could
not read anyway.
85.1.57.1.4. Options¶
- –local-domain DOMAIN
A domain whose senders count as “us”. Repeatable. At least one –local-domain or –own-address is required — without one, no message can be recognised as sent by us.
- –own-address ADDRESS
Count only mail from this exact address as ours, rather than any sender at a local domain. Repeatable.
- –include-delivered
Drop the delivery-trace test and count a message as ours on its
From:alone.This is unsafe on any mailbox that holds received mail: a forged
From:is then enough to make an inbound message look sent, and itsTo:addresses become whitelist entries. It exists for the unusual store whose sent copies really did go through delivery — a provider that files its ownReceived:- bearing copy inSent— and should be pointed at that folder only, never at an inbox.- –format
auto|mbox|maildir Format of the given paths.
auto(the default) treats a directory as a Maildir tree and a file as an mbox.An mbox is split on
From `` lines that stand at the start of the file or directly after a blank line. A Maildir is read from ``cur/andnew/(nevertmp/, whose files may be half-written), and every directory below the one given that itself containscur/ornew/is scanned too — so the Maildir++ layout (.Sent,.Archive.2025) is covered without naming each folder.- –imap URL
Scan a remote store instead of files.
imaps://user@host[:port]/[PATTERN]uses implicit TLS (port 993),imap://usesSTARTTLS(port 143), andimap+plain://disables TLS entirely (which sends the password in the clear, and is only sensible against a loopback test server). PATTERN is an IMAP mailbox pattern,*by default.The server certificate is always verified against the system trust store. Mailboxes are opened with
EXAMINE(read-only) and onlyBODY.PEEK[HEADER]is fetched, so a scan neither marks messages\Seennor changes anything else.- –imap-user NAME
IMAP login, if it is not in the URL.
- –imap-password-file FILE
Read the password from the first line of FILE, which must grant no access to its group or to other users (for example mode
0600).- –imap-password-stdin
Read the password from the first line of standard input. This is how pepsi-whitelist(1) passes it, so it never appears in a command line where other users could read it out of
ps.- –doveadm-stdin
Read a
doveadm -f json fetch -uLOGINhdr mailbox '*' allstream from standard input instead of opening files. Used for Dovecot’s own mail formats (mdbox/sdbox): the caller runs the privilegeddoveadm, and only its output crosses into this unprivileged process. JSON is required because a header block contains blank lines and colons of its own, which doveadm’s human-readable formats do not escape.- –max-header-bytes BYTES
Cap on the header bytes retained per message (default 65536).
- –max-messages N
Examine at most N messages (0, the default, means no limit).
This is a cap on how many messages are parsed and classified, which is the expensive part per message — it does not abort the walk. The mbox file is still read to its end, the Maildir tree is still walked, and an IMAP session still fetches every mailbox’s headers. Use it to bound the work of a scan, not the time it takes on a very large store.
- -h, –help
Print a usage summary and exit.
- -V, –version
Print the version and exit.
85.1.57.1.5. Output¶
One tab-separated record per line on standard output:
a<TAB>ADDRESSA correspondent address, emitted at most once.
m<TAB>COUNTHow many messages were examined.
s<TAB>COUNTHow many of them were sent by us.
w<TAB>MESSAGEA non-fatal problem: an unreadable folder, a skipped record, a mailbox with no
cur/new. Warnings are emitted after the addresses and are capped.
A reader ignores record types it does not know, so later versions may add some.
85.1.57.1.6. Exit Status¶
- 0
The scan completed. Any
wrecords are non-fatal.- 1
A fatal error: bad arguments, an unreadable mailbox path, an IMAP login refused. The diagnostic is on standard error.
85.1.57.1.7. Examples¶
Scan your own mail:
pepsi-helper-mailbox-scan --local-domain example.com ~/Maildir /var/mail/alice
Only mail you sent from one particular address:
pepsi-helper-mailbox-scan --own-address alice@example.com ~/Maildir
A remote account over IMAP, with the password in a file:
pepsi-helper-mailbox-scan --local-domain example.com \
--imap imaps://alice@mail.example.com/ --imap-password-file ~/.imap-pw
A Dovecot store, as root:
doveadm -f json fetch -u alice hdr mailbox '*' all \
| pepsi-helper-mailbox-scan --local-domain example.com --doveadm-stdin
85.1.57.1.8. See Also¶
pepsi-whitelist(1), pepsi-stage-check-whitelist(1), pepsi-stage-auto-whitelist(1), pepsi.conf(5)
85.1.57.1.9. Bugs¶
Report bugs to the Pepsi issue tracker.