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
+25 -15
View File
@@ -46,10 +46,10 @@ pub struct Network {
#[derive(Clone)]
pub struct NetworkInfo {
pub pacc: Pacc,
/// inbuxa: the same document without IMAP, POP3, SMTP and ManageSieve,
/// served while legacy protocols are off (legacy-protocols LP-7).
pub pacc_jmap_only: Pacc,
/// inbuxa: the document once per combination of legacy protocols off,
/// indexed by `LegacyOff::index` (legacy-protocols LP-7, one switch per
/// protocol); index 0 is the full document.
pub pacc: Vec<Pacc>,
pub mxs: Vec<MailExchanger>,
pub services: VecMap<ServiceProtocol, Service>,
}
@@ -333,16 +333,27 @@ impl Network {
})
.unwrap()
};
// inbuxa: legacy-protocols LP-7
let pacc_jmap_only = {
let mut pacc = pacc.clone();
pacc.protocols.imap = None;
pacc.protocols.pop3 = None;
pacc.protocols.smtp = None;
pacc.protocols.managesieve = None;
split(&pacc)
};
let pacc = split(&pacc);
// inbuxa: legacy-protocols LP-7, one document per combination of
// protocols off, bits as `LegacyOff::index`: IMAP, POP3, ManageSieve,
// submission.
let pacc = (0..16usize)
.map(|off| {
let mut pacc = pacc.clone();
if off & 1 != 0 {
pacc.protocols.imap = None;
}
if off & 2 != 0 {
pacc.protocols.pop3 = None;
}
if off & 4 != 0 {
pacc.protocols.managesieve = None;
}
if off & 8 != 0 {
pacc.protocols.smtp = None;
}
split(&pacc)
})
.collect();
let mut network = Network {
node_id: bp.node_id() as u64,
server_name: default_hostname.to_string(),
@@ -358,7 +369,6 @@ impl Network {
mxs: system.mail_exchangers.into_iter().collect(),
services: system.services,
pacc,
pacc_jmap_only,
},
};