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

pepsi-quota [GLOBAL-OPTIONS] set LOGIN SIZE [–messages N]
pepsi-quota [GLOBAL-OPTIONS] unset LOGIN
pepsi-quota [GLOBAL-OPTIONS] show LOGIN
pepsi-quota [GLOBAL-OPTIONS] list [–full]
pepsi-quota [GLOBAL-OPTIONS] measure LOGIN
pepsi-quota [GLOBAL-OPTIONS] reconcile [–all]
pepsi-quota [GLOBAL-OPTIONS] remove LOGIN

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/T suffix in powers of 1024 (2G, 500M, 1048576), or the literal none.

none is not the same as unset: it means explicitly unlimited, and is how a single account is exempted from a site-wide quota, whereas unset restores inheritance of it.

Omitting --messages leaves the message-count limit inheriting [pepsi] MAILBOX_QUOTA_COUNT; setting a byte quota does not silently lift it. --messages 0 lifts it explicitly. Note that this is a property of the stored row, not of the command: set writes the whole row, so omitting --messages on an account that already carried an explicit message limit discards that limit and returns it to inheritance. Repeat --messages when 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 root and the pepsi service account get as far as reading it.

-L LOGLEVEL, –log LOGLEVEL

Set the logging verbosity. LOGLEVEL is one of error, warn, info, debug or trace (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 the PATH of the pepsi-quota process unless given as an absolute path. It is the same binary the delivery stage uses, in its measure mode: a Maildir is 0700, 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/bin is not on the timer’s PATH. Keep it in step with the HELPER option 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_quota

The 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/maildirsize

The 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.