Files
inbuxa-server/crates/common/src/network/autoconfig/legacy_autoconfig.rs
T
jcoffey-dev 8e9cedbe97
ci / fork-checks (pull_request) Successful in 43s
ci / build (pull_request) Successful in 7m40s
Give IMAP, POP3 and ManageSieve a switch each
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.
2026-09-27 23:12:32 -07:00

114 lines
4.6 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*
* Modified by Coffey Labs in 2026 for INBUXA.
*/
use crate::{Server, manager::application::Resource};
use registry::schema::enums::ServiceProtocol;
use std::fmt::Write;
use utils::url_params::UrlParams;
impl Server {
pub async fn handle_autoconfig_request(
&self,
uri: Option<&str>,
) -> trc::Result<Resource<Vec<u8>>> {
// Obtain parameters
let params = UrlParams::new(uri);
let emailaddress_param = params
.get("emailaddress")
.unwrap_or_default()
.to_lowercase();
let default_host = &self.core.network.server_name;
let (emailaddress, domain) = if let Some((_, domain)) = emailaddress_param.rsplit_once('@')
{
(emailaddress_param.as_str(), domain)
} else {
("%EMAILADDRESS%", default_host.as_str())
};
// inbuxa: legacy-protocols LP-7, LP-14a
let legacy_off = self.legacy_off_for(domain).await?;
// Build XML response
let mut config = String::with_capacity(1024);
config.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
config.push_str("<clientConfig version=\"1.1\">\n");
let _ = writeln!(&mut config, "\t<emailProvider id=\"{domain}\">");
let _ = writeln!(&mut config, "\t\t<domain>{domain}</domain>");
let _ = writeln!(&mut config, "\t\t<displayName>{emailaddress}</displayName>");
let _ = writeln!(
&mut config,
"\t\t<displayShortName>{domain}</displayShortName>"
);
for (protocol, service) in &self.core.network.info.services {
if legacy_off.service(protocol) {
continue;
}
let (protocol, tag, ports) = match protocol {
ServiceProtocol::Smtp => ("smtp", "outgoingServer", [587, 465]),
ServiceProtocol::Imap => ("imap", "incomingServer", [143, 993]),
ServiceProtocol::Pop3 => ("pop3", "incomingServer", [110, 995]),
_ => continue,
};
for (is_tls, port) in ports.into_iter().enumerate() {
if is_tls == 1 || service.cleartext {
let server_name = service.hostname.as_deref().unwrap_or(default_host);
let _ = writeln!(&mut config, "\t\t<{tag} type=\"{protocol}\">");
let _ = writeln!(&mut config, "\t\t\t<hostname>{server_name}</hostname>");
let _ = writeln!(&mut config, "\t\t\t<port>{port}</port>");
let _ = writeln!(
&mut config,
"\t\t\t<socketType>{}</socketType>",
if is_tls == 1 { "SSL" } else { "STARTTLS" }
);
let _ = writeln!(&mut config, "\t\t\t<username>{emailaddress}</username>");
let _ = writeln!(
&mut config,
"\t\t\t<authentication>password-cleartext</authentication>"
);
let _ = writeln!(&mut config, "\t\t</{tag}>");
}
}
}
config.push_str("\t</emailProvider>\n");
for (protocol, service) in &self.core.network.info.services {
let (tag, protocol, url) = match protocol {
ServiceProtocol::Carddav => ("addressBook", "carddav", "card"),
ServiceProtocol::Caldav => ("calendar", "caldav", "cal"),
ServiceProtocol::Webdav => ("fileShare", "webdav", "file"),
_ => continue,
};
let server_name = service.hostname.as_deref().unwrap_or(default_host);
let _ = writeln!(&mut config, "\t<{tag} type=\"{protocol}\">");
let _ = writeln!(&mut config, "\t\t<username>{emailaddress}</username>");
let _ = writeln!(
&mut config,
"\t\t<authentication>http-basic</authentication>"
);
let _ = writeln!(
&mut config,
"\t\t<serverURL>https://{server_name}/dav/{url}</serverURL>"
);
let _ = writeln!(&mut config, "\t</{tag}>");
}
let _ = writeln!(
&mut config,
"\t<clientConfigUpdate url=\"https://autoconfig.{domain}/mail/config-v1.1.xml\"></clientConfigUpdate>"
);
config.push_str("</clientConfig>\n");
Ok(Resource::new(
"application/xml; charset=utf-8",
config.into_bytes(),
))
}
}