85.1.52. pepsi-quota¶
Manage and reconcile per-account mailbox quotas
- Manual section:
1
85.1.52.1.1. Name¶
pepsi-quota - set per-account mailbox quotas and re-measure mailboxes.
85.1.52.1.2. Synopsis¶
GLOBAL-OPTIONS are -c FILE, -L LOGLEVEL and -v, and precede the sub-command (see Global Options).
85.1.52.1.3. Description¶
pepsi-quota is the operator interface to the pepsi.mailbox_quota table:
the per-account mailbox limits that pepsi-stage-relay-to-maildir(1)
enforces, and the usage accounting both it and pepsi-ingress(1) work from.
It is not a stage.
Quota is a property of a local account, so every row is keyed on the passwd
login — not on an e-mail address, and not on a uid. A login is what
pepsi-ingress(1) can compare an envelope recipient against at RCPT time
without resolving aliases (which it must not do, and cannot), and it stays one
row when several addresses reach one mailbox.
85.1.52.1.3.1. Policy, measurement, estimate¶
Policy is [pepsi] MAILBOX_QUOTA (the site default; absent means unlimited),
overridden per account by pepsi-quota set, and tightened further by the
kernel’s own limit where the filesystem enforces one.
Measurement can only be taken by pepsi-helper-maildir-writer(1), which is
the single process that becomes the user and can read a 0700 Maildir.
measure and reconcile run it; nothing else here touches a mailbox.
Estimate is the used_* + since_* counters. Pepsi’s own deliveries add to
them and nothing ever subtracts, because nothing tells Pepsi when a user
deletes mail over IMAP. The estimate is therefore an upper bound on real usage —
which is exactly what makes it safe as a trigger (“could this account be near
its limit? then measure”) and unsafe as a verdict.
Hence the rule: nothing refuses a message on an estimate, only on a measurement.
85.1.52.1.3.2. Why reconcile exists¶
An account at its limit is refused at RCPT by pepsi-ingress(1). Suppose
its owner then empties the mailbox over IMAP. Nothing tells Pepsi — and since
every message is being refused, no delivery ever runs to take a fresh
measurement. The mailbox would stay shut for ever.
Two things prevent that. pepsi-ingress(1) refuses only on a measurement
younger than [pepsi] MAILBOX_QUOTA_MAX_AGE (15 minutes by default), so a
stale figure lets the message through and the delivery path looks again. And
pepsi-quota reconcile, run from cron, re-measures the accounts that are at
their limit — a cheap sweep precisely because it is only those.
A site that enforces quotas must run it periodically. On a package install this
is already done: pepsi-quota-reconcile.timer runs the sweep five minutes after
boot and every ten minutes thereafter (Persistent=true, so a missed run is
made up), and pepsi.target Wants= it — the one switch that starts the
pipeline starts this too. Do not add a cron job beside it; that is two sweeps
at the same interval, each spawning the setuid helper.
On a source install without systemd, the equivalent is:
*/10 * * * * pepsi pepsi-quota -c /etc/pepsi/pepsi.conf reconcile
85.1.52.1.4. Sub-commands¶
- set LOGIN SIZE [–messages N]
Give one account its own limit, overriding the site default. SIZE accepts a plain byte count or a
K/M/G/Tsuffix in powers of 1024 (2G,500M,1048576), or the literalnone.noneis not the same asunset: it means explicitly unlimited, and is how a single account is exempted from a site-wide quota, whereasunsetrestores inheritance of it.Omitting
--messagesleaves the message-count limit inheriting[pepsi] MAILBOX_QUOTA_COUNT; setting a byte quota does not silently lift it.--messages 0lifts it explicitly. Note that this is a property of the stored row, not of the command: set writes the whole row, so omitting--messageson an account that already carried an explicit message limit discards that limit and returns it to inheritance. Repeat--messageswhen raising a byte quota on such an account; the printed row shows the result either way.A login with no passwd entry is warned about but accepted — the account may be created afterwards.
- unset LOGIN
Drop the account’s own limits, so it inherits the site default again. The usage accounting is kept.
- show LOGIN
Show one account’s limits and usage. An account with no row is not an error: it prints the inherited default and “never measured”.
- list [–full]
List every account Pepsi has accounted for, oldest measurement first. With
--full, only those at or over their limit — and an empty listing then prints(no accounts at or over their limit)rather than(no accounts), which is the one case worth telling apart.- measure LOGIN
Re-measure this mailbox now and store the result.
- reconcile [–all]
Re-measure the accounts that are at or over their limit. With
--all, also those whose measurement has merely gone stale. One unmeasurable mailbox (an account deleted since its last delivery, say) is logged and the sweep continues; the exit status is non-zero if any failed.- remove LOGIN
Forget the account entirely — its limits and its accounting. The next delivery re-creates the row and measures afresh, so this is also the “these numbers are wrong, start over” button.
85.1.52.1.5. Global Options¶
These global options precede the subcommand (a trailing flag is rejected).
- -c FILE, –config FILE
Read the configuration from FILE instead of searching the default locations. Unlike pepsi-whitelist(1), this is not refused under the setgid bit: the caller check described under Privileges runs first, so only
rootand thepepsiservice account get as far as reading it.- -L LOGLEVEL, –log LOGLEVEL
Set the logging verbosity. LOGLEVEL is one of
error,warn,info,debugortrace(default:info).- -v, –verbose
Show log messages from all sources, including third-party libraries.
- -h, –help
Print a usage summary and exit.
- -V, –version
Print the version and exit.
85.1.52.1.6. Output¶
list, show, set and measure print one line per account:
alice 1200M/2G (58%) 4213 messages measured 91s ago (maildirsize)
bob 14M/unlimited 112 messages measured never (-) [site default]
The usage shown is the measurement plus everything delivered since, which is
the figure the delivery path acts on. measured is an age rather than a
timestamp because the question it answers is whether the figure is still worth
believing: a refusal at RCPT rests on a measurement being fresh.
85.1.52.1.7. Privileges¶
pepsi-quota is installed set-group-id to the pepsi-maildir group
(mode 2550, owner pepsi:pepsi-maildir — owner-execute and not
world-execute, because the setgid bit is the gate on the setuid-root helper,
and the exec right cannot come from the group bits either, pepsi being
deliberately not a member), for the same reason
pepsi-stage-relay-to-maildir(1) is: measure and reconcile run the
4750 root:pepsi-maildir measuring helper, and the pepsi service account is
deliberately not a member of that group — the setgid bit on the few programs
allowed through is the gate. That is what lets an unattended reconcile from
cron measure mailboxes without being root.
Run as root it drops to the pepsi service account before connecting to the
database, which is the role that owns these rows — keeping the
pepsi-maildir group across the drop. Run from its timer as pepsi the
setgid bit supplies that group and no drop happens at all; keeping it across the
drop is what makes measure work for an operator typing it as root just as it
does from cron. Root’s run thereby gains nothing the timer’s run does not already
have.
make install sets the bit up (its install-quota-tool step) provided it is
run as root and the group exists; otherwise it prints the exact commands.
The setgid bit being that gate, the program states the same rule itself: unless
the real user is root or the pepsi service account it exits with an error
before any configuration is read, and — like the setuid crypto stages — it strips
the environment variables that steer configuration loading (HOME,
XDG_CONFIG_HOME, PG*, TALER_*, PEPSI_*) and pins PATH to a
safe default.
No unprivileged caller loses anything by that refusal: every subcommand reads or
writes pepsi.mailbox_quota, and PostgreSQL peer authentication keys off the
effective uid, so a caller who is not pepsi (or root, which becomes pepsi)
could never have connected.
85.1.52.1.8. Configuration¶
The policy options this tool reads — MAILBOX_QUOTA, MAILBOX_QUOTA_COUNT,
MAILBOX_OVER_QUOTA, MAILBOX_QUOTA_MAX_AGE, MAILBOX_QUOTA_RCPT_CHECK
and MAILBOX_FS_QUOTA — all live in the [pepsi] section and are documented
in pepsi.conf(5). One more, also in [pepsi], belongs to this tool in
particular:
- MAILBOX_HELPER
(path, optional) The privileged helper this tool runs to take a measurement. Default
pepsi-helper-maildir-writer, located on thePATHof thepepsi-quotaprocess unless given as an absolute path. It is the same binary the delivery stage uses, in itsmeasuremode: aMaildiris0700, so it is the one process that can become the user and read one.Set it only where the helper is not reachable under that name — chiefly a
--prefix-ed source install whose$PREFIX/binis not on the timer’sPATH. Keep it in step with theHELPERoption in the delivery stage’s own[stage-<name>]section (pepsi-stage-relay-to-maildir(1)): the two name the same program for two different callers, nothing cross-checks them, and only the pair being right makes delivery and reconciliation agree.
85.1.52.1.9. Files¶
pepsi.mailbox_quotaThe table this tool manages: one row per account, carrying its limits, the last measurement and what has been delivered since. Created by pepsi-setup(1) with the rest of the schema; rows appear by themselves on an account’s first local delivery, so nobody has to be pre-registered.
~/Maildir/maildirsizeThe Maildir++ size file the helper maintains, in the format Dovecot, Courier and Exim also read and write.
85.1.52.1.10. See Also¶
pepsi-stage-relay-to-maildir(1), pepsi-helper-maildir-writer(1), pepsi-ingress(1), pepsi-status(1), pepsi.conf(5), maildir(5), quotactl(2)
85.1.52.1.11. Bugs¶
Report bugs to the Pepsi issue tracker.