Give IMAP, POP3 and ManageSieve a switch each
ci / fork-checks (pull_request) Successful in 43s
ci / build (pull_request) Successful in 7m40s

The legacy-protocols switch was all or nothing. An operator can now stop
POP3 and keep IMAP: each of IMAP, POP3 and ManageSieve has its own
switch, server-wide on inbuxa:ProtocolPolicy and per tenant on
inbuxa:TenantProtocolPolicy (properties imap, pop3, manageSieve).

legacyProtocols stays as the kill-all: setting it sets all three, and it
reads "disabled" exactly when all three are off. A policy stored before
this has only legacyProtocols and reads as all three at that value, so
existing servers and tenants carry over unchanged. In one /set, a
protocol named beside legacyProtocols overrides it.

SMTP submission keeps no switch of its own: sign-in over it is refused
only when all three are off, as the single switch did (LP-6), so
turning one protocol off never stops a mail app sending. For a tenant,
the server's switches and the tenant's count together.

Server-wide, a change closes the listeners of whatever is now off and
puts back the saved listeners of whatever is on again, both in one
change if asked; listeners of a protocol still off stay saved. Sign-in,
autoconfig, autodiscover, PACC (now prepared once per combination) and
the suggested DNS records all follow each protocol separately. A tenant
may turn a protocol on only while the server has it on (LP-9), and the
refusal names which. The JMAP session adds legacyAllowed, the protocols
still allowed for the account; legacyProtocols there keeps its meaning
for older webmail builds. Events name the switches ("pop3 disabled"),
and audit before/after reads every switch even from an older policy.

Tested: unit tests for the switches, the old-policy reading, the
server/tenant combination, the tenant refusal and listener refusal; and
tests/e2e/legacy_protocols.py against a running server, all 100 checks,
including new ones: POP3 alone off closes only its port and refuses
only its sign-in while IMAP and sending go on; only POP3 stops being
advertised; one change closes IMAP and reopens POP3; a tenant turns
POP3 off for itself, and can't turn IMAP on while the server has it off.
This commit is contained in:
2026-09-27 23:12:32 -07:00
parent 7ba9ec9fa0
commit 8e9cedbe97
15 changed files with 930 additions and 199 deletions
@@ -23,8 +23,13 @@ pub struct ProtocolPolicy;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ProtocolPolicyProperty {
Id,
/// The switch: `enabled` or `disabled`.
/// The kill-all: `enabled` or `disabled`; reads `disabled` when all
/// three protocols are off, and sets all three.
LegacyProtocols,
/// Each protocol's own switch: `enabled` or `disabled`.
Imap,
Pop3,
ManageSieve,
/// Whether submission closes with it. Forced false while SMTP is locked.
CloseSubmission,
/// Server-set: the listeners taken away, for LP-5.
@@ -56,6 +61,9 @@ impl Property for ProtocolPolicyProperty {
match self {
ProtocolPolicyProperty::Id => "id",
ProtocolPolicyProperty::LegacyProtocols => "legacyProtocols",
ProtocolPolicyProperty::Imap => "imap",
ProtocolPolicyProperty::Pop3 => "pop3",
ProtocolPolicyProperty::ManageSieve => "manageSieve",
ProtocolPolicyProperty::CloseSubmission => "closeSubmission",
ProtocolPolicyProperty::SavedListeners => "savedListeners",
ProtocolPolicyProperty::ChangedAt => "changedAt",
@@ -73,6 +81,9 @@ impl ProtocolPolicyProperty {
hashify::tiny_map!(value.as_bytes(),
b"id" => ProtocolPolicyProperty::Id,
b"legacyProtocols" => ProtocolPolicyProperty::LegacyProtocols,
b"imap" => ProtocolPolicyProperty::Imap,
b"pop3" => ProtocolPolicyProperty::Pop3,
b"manageSieve" => ProtocolPolicyProperty::ManageSieve,
b"closeSubmission" => ProtocolPolicyProperty::CloseSubmission,
b"savedListeners" => ProtocolPolicyProperty::SavedListeners,
b"changedAt" => ProtocolPolicyProperty::ChangedAt,