Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79b6787397 | ||
|
|
64cddc9246 | ||
|
|
4b585905d7 |
@@ -47,6 +47,9 @@ pub struct Network {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct NetworkInfo {
|
pub struct NetworkInfo {
|
||||||
pub pacc: Pacc,
|
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,
|
||||||
pub mxs: Vec<MailExchanger>,
|
pub mxs: Vec<MailExchanger>,
|
||||||
pub services: VecMap<ServiceProtocol, Service>,
|
pub services: VecMap<ServiceProtocol, Service>,
|
||||||
}
|
}
|
||||||
@@ -320,11 +323,26 @@ impl Network {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (prefix, suffix) = serde_json::to_string(&pacc)
|
let split = |pacc: &Configuration| {
|
||||||
.unwrap_or_default()
|
serde_json::to_string(pacc)
|
||||||
.rsplit_once(SPLIT_HERE)
|
.unwrap_or_default()
|
||||||
.map(|(prefix, suffix)| (prefix.to_string(), suffix.to_string()))
|
.rsplit_once(SPLIT_HERE)
|
||||||
.unwrap();
|
.map(|(prefix, suffix)| Pacc {
|
||||||
|
prefix: prefix.to_string(),
|
||||||
|
suffix: suffix.to_string(),
|
||||||
|
})
|
||||||
|
.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);
|
||||||
let mut network = Network {
|
let mut network = Network {
|
||||||
node_id: bp.node_id() as u64,
|
node_id: bp.node_id() as u64,
|
||||||
server_name: default_hostname.to_string(),
|
server_name: default_hostname.to_string(),
|
||||||
@@ -339,7 +357,8 @@ impl Network {
|
|||||||
info: NetworkInfo {
|
info: NetworkInfo {
|
||||||
mxs: system.mail_exchangers.into_iter().collect(),
|
mxs: system.mail_exchangers.into_iter().collect(),
|
||||||
services: system.services,
|
services: system.services,
|
||||||
pacc: Pacc { prefix, suffix },
|
pacc,
|
||||||
|
pacc_jmap_only,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
* 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 crate::{Server, manager::application::Resource, network::legacy::is_legacy_service};
|
||||||
use quick_xml::Reader;
|
use quick_xml::Reader;
|
||||||
use quick_xml::XmlVersion;
|
use quick_xml::XmlVersion;
|
||||||
use quick_xml::events::Event;
|
use quick_xml::events::Event;
|
||||||
@@ -55,7 +57,12 @@ impl Server {
|
|||||||
let _ = writeln!(&mut config, "\t\t<Account>");
|
let _ = writeln!(&mut config, "\t\t<Account>");
|
||||||
let _ = writeln!(&mut config, "\t\t\t<AccountType>email</AccountType>");
|
let _ = writeln!(&mut config, "\t\t\t<AccountType>email</AccountType>");
|
||||||
let _ = writeln!(&mut config, "\t\t\t<Action>settings</Action>");
|
let _ = writeln!(&mut config, "\t\t\t<Action>settings</Action>");
|
||||||
|
// inbuxa: legacy-protocols LP-7
|
||||||
|
let legacy_off = self.legacy_protocols_off().await?;
|
||||||
for (protocol, service) in &self.core.network.info.services {
|
for (protocol, service) in &self.core.network.info.services {
|
||||||
|
if legacy_off && is_legacy_service(protocol) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let (protocol, ports) = match protocol {
|
let (protocol, ports) = match protocol {
|
||||||
ServiceProtocol::Imap => ("IMAP", [143, 993]),
|
ServiceProtocol::Imap => ("IMAP", [143, 993]),
|
||||||
ServiceProtocol::Pop3 => ("POP3", [110, 995]),
|
ServiceProtocol::Pop3 => ("POP3", [110, 995]),
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
* 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 crate::{Server, manager::application::Resource, network::legacy::is_legacy_service};
|
||||||
use registry::schema::enums::ServiceProtocol;
|
use registry::schema::enums::ServiceProtocol;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use utils::url_params::UrlParams;
|
use utils::url_params::UrlParams;
|
||||||
@@ -28,6 +30,9 @@ impl Server {
|
|||||||
("%EMAILADDRESS%", default_host.as_str())
|
("%EMAILADDRESS%", default_host.as_str())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// inbuxa: legacy-protocols LP-7
|
||||||
|
let legacy_off = self.legacy_protocols_off().await?;
|
||||||
|
|
||||||
// Build XML response
|
// Build XML response
|
||||||
let mut config = String::with_capacity(1024);
|
let mut config = String::with_capacity(1024);
|
||||||
config.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
config.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
||||||
@@ -40,6 +45,9 @@ impl Server {
|
|||||||
"\t\t<displayShortName>{domain}</displayShortName>"
|
"\t\t<displayShortName>{domain}</displayShortName>"
|
||||||
);
|
);
|
||||||
for (protocol, service) in &self.core.network.info.services {
|
for (protocol, service) in &self.core.network.info.services {
|
||||||
|
if legacy_off && is_legacy_service(protocol) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let (protocol, tag, ports) = match protocol {
|
let (protocol, tag, ports) = match protocol {
|
||||||
ServiceProtocol::Smtp => ("smtp", "outgoingServer", [587, 465]),
|
ServiceProtocol::Smtp => ("smtp", "outgoingServer", [587, 465]),
|
||||||
ServiceProtocol::Imap => ("imap", "incomingServer", [143, 993]),
|
ServiceProtocol::Imap => ("imap", "incomingServer", [143, 993]),
|
||||||
|
|||||||
@@ -2,9 +2,15 @@
|
|||||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||||
|
*
|
||||||
|
* Modified by Coffey Labs in 2026 for INBUXA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::{Server, config::network::Pacc, network::dkim::generate_dkim_dns_record};
|
use crate::{
|
||||||
|
Server,
|
||||||
|
config::network::Pacc,
|
||||||
|
network::{dkim::generate_dkim_dns_record, legacy::is_legacy_service},
|
||||||
|
};
|
||||||
use ahash::{AHashMap, AHashSet};
|
use ahash::{AHashMap, AHashSet};
|
||||||
use base64::{Engine, engine::general_purpose};
|
use base64::{Engine, engine::general_purpose};
|
||||||
use dns_update::{
|
use dns_update::{
|
||||||
@@ -33,6 +39,8 @@ impl Server {
|
|||||||
let mut records = Vec::new();
|
let mut records = Vec::new();
|
||||||
let network = &self.core.network;
|
let network = &self.core.network;
|
||||||
let default_host = network.server_name.as_str();
|
let default_host = network.server_name.as_str();
|
||||||
|
// inbuxa: legacy-protocols LP-7
|
||||||
|
let legacy_off = self.legacy_protocols_off().await?;
|
||||||
let domain_name = domain.name.as_str();
|
let domain_name = domain.name.as_str();
|
||||||
let domain_name_suffix = format!(".{domain_name}");
|
let domain_name_suffix = format!(".{domain_name}");
|
||||||
|
|
||||||
@@ -193,6 +201,25 @@ impl Server {
|
|||||||
ServiceProtocol::Smtp => [("submission", 587), ("submissions", 465)],
|
ServiceProtocol::Smtp => [("submission", 587), ("submissions", 465)],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// inbuxa: legacy-protocols LP-7. While they are off, every
|
||||||
|
// name says "not offered" -- target "." (RFC 6186 section
|
||||||
|
// 3.4) -- rather than vanishing, so a client that looks
|
||||||
|
// is told, and an old record left in the zone is replaced.
|
||||||
|
if legacy_off && is_legacy_service(protocol) {
|
||||||
|
for (service_name, _) in services {
|
||||||
|
records.push(NamedDnsRecord {
|
||||||
|
name: format!("_{service_name}._tcp.{domain_name}."),
|
||||||
|
record: DnsRecord::SRV(SRVRecord {
|
||||||
|
target: ".".to_string(),
|
||||||
|
priority: 0,
|
||||||
|
weight: 0,
|
||||||
|
port: 0,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (is_tls, (service_name, port)) in services.into_iter().enumerate() {
|
for (is_tls, (service_name, port)) in services.into_iter().enumerate() {
|
||||||
if is_tls == 1 || service.cleartext {
|
if is_tls == 1 || service.cleartext {
|
||||||
records.push(NamedDnsRecord {
|
records.push(NamedDnsRecord {
|
||||||
@@ -277,6 +304,14 @@ impl Server {
|
|||||||
for (protocol, service) in &network.info.services {
|
for (protocol, service) in &network.info.services {
|
||||||
let hostname = service.hostname.as_deref().unwrap_or(default_host);
|
let hostname = service.hostname.as_deref().unwrap_or(default_host);
|
||||||
if hostname.ends_with(&domain_name_suffix) || hostname == domain_name {
|
if hostname.ends_with(&domain_name_suffix) || hostname == domain_name {
|
||||||
|
// inbuxa: legacy-protocols LP-7. No TLS pin for a port
|
||||||
|
// the switch has closed. Submission's port stays open
|
||||||
|
// (the SMTP lock), so its record stays.
|
||||||
|
if legacy_off
|
||||||
|
&& matches!(protocol, ServiceProtocol::Imap | ServiceProtocol::Pop3)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let port = match protocol {
|
let port = match protocol {
|
||||||
ServiceProtocol::Imap => 993,
|
ServiceProtocol::Imap => 993,
|
||||||
ServiceProtocol::Pop3 => 995,
|
ServiceProtocol::Pop3 => 995,
|
||||||
@@ -382,6 +417,12 @@ impl Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_pacc_for_domain(&self, domain_name: &str) -> trc::Result<String> {
|
pub async fn get_pacc_for_domain(&self, domain_name: &str) -> trc::Result<String> {
|
||||||
|
// inbuxa: legacy-protocols LP-7
|
||||||
|
let pacc = if self.legacy_protocols_off().await? {
|
||||||
|
&self.core.network.info.pacc_jmap_only
|
||||||
|
} else {
|
||||||
|
&self.core.network.info.pacc
|
||||||
|
};
|
||||||
self.get_directory_for_domain(domain_name)
|
self.get_directory_for_domain(domain_name)
|
||||||
.await
|
.await
|
||||||
.caused_by(trc::location!())
|
.caused_by(trc::location!())
|
||||||
@@ -390,15 +431,9 @@ impl Server {
|
|||||||
.and_then(|directory| {
|
.and_then(|directory| {
|
||||||
directory
|
directory
|
||||||
.oidc_discovery_document()
|
.oidc_discovery_document()
|
||||||
.map(|doc| self.core.network.info.pacc.build(&doc.url))
|
.map(|doc| pacc.build(&doc.url))
|
||||||
})
|
|
||||||
.unwrap_or_else(|| {
|
|
||||||
self.core
|
|
||||||
.network
|
|
||||||
.info
|
|
||||||
.pacc
|
|
||||||
.build(&self.core.network.http.url_https)
|
|
||||||
})
|
})
|
||||||
|
.unwrap_or_else(|| pacc.build(&self.core.network.http.url_https))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,10 @@
|
|||||||
//! a legacy protocol is refused before any password is looked at, so a
|
//! a legacy protocol is refused before any password is looked at, so a
|
||||||
//! listener that exists by mistake still lets nobody in.
|
//! listener that exists by mistake still lets nobody in.
|
||||||
//!
|
//!
|
||||||
|
//! And nothing advertises what is closed (LP-7): client configuration and
|
||||||
|
//! the suggested DNS records leave the legacy services out, or mark them as
|
||||||
|
//! not offered, while the switch is off.
|
||||||
|
//!
|
||||||
//! Nothing here touches the host's firewall, NAT port-forwards or any proxy
|
//! Nothing here touches the host's firewall, NAT port-forwards or any proxy
|
||||||
//! (LP-20). The server stops answering; what still routes the port is the
|
//! (LP-20). The server stops answering; what still routes the port is the
|
||||||
//! operator's to reconcile.
|
//! operator's to reconcile.
|
||||||
@@ -31,6 +35,7 @@ use inbuxa_features::security::{
|
|||||||
listeners,
|
listeners,
|
||||||
protocol_policy::{self, ProtocolPolicy, SavedListener},
|
protocol_policy::{self, ProtocolPolicy, SavedListener},
|
||||||
};
|
};
|
||||||
|
use registry::schema::enums::ServiceProtocol;
|
||||||
use registry::types::{error::Error, id::ObjectId};
|
use registry::types::{error::Error, id::ObjectId};
|
||||||
use store::registry::bootstrap::Bootstrap;
|
use store::registry::bootstrap::Bootstrap;
|
||||||
|
|
||||||
@@ -102,6 +107,37 @@ impl Server {
|
|||||||
|
|
||||||
protocol_policy::set(&self.core.storage.data, &policy).await?;
|
protocol_policy::set(&self.core.storage.data, &policy).await?;
|
||||||
|
|
||||||
|
// LP-8. Raised here rather than by the JMAP method, so whatever turns
|
||||||
|
// the switch is reported. A /set that changed nothing -- the switch
|
||||||
|
// already where it was asked to be, nothing to close or reopen -- is
|
||||||
|
// not a change.
|
||||||
|
if previous.legacy_protocols != policy.legacy_protocols || !change.is_empty() {
|
||||||
|
let (moved, direction) = if policy.legacy_protocols.is_disabled() {
|
||||||
|
(&change.closed, "closed")
|
||||||
|
} else {
|
||||||
|
(&change.reopened, "reopened")
|
||||||
|
};
|
||||||
|
trc::event!(
|
||||||
|
Security(trc::SecurityEvent::LegacyProtocolsChanged),
|
||||||
|
Policy = "server",
|
||||||
|
Value = if policy.legacy_protocols.is_disabled() {
|
||||||
|
"disabled"
|
||||||
|
} else {
|
||||||
|
"enabled"
|
||||||
|
},
|
||||||
|
AccountId = policy.changed_by.clone(),
|
||||||
|
Details = direction,
|
||||||
|
ListenerId = listener_names(moved.iter().map(|l| l.id.clone())),
|
||||||
|
// Only when a listener could not be put back (LP-5).
|
||||||
|
Reason = (!change.failed.is_empty()).then(|| listener_names(
|
||||||
|
change
|
||||||
|
.failed
|
||||||
|
.iter()
|
||||||
|
.map(|(l, why)| format!("{}: {why}", l.id))
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(change)
|
Ok(change)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,6 +251,12 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Names for an event field: the listeners a change closed, reopened or
|
||||||
|
/// failed to reopen (LP-8).
|
||||||
|
fn listener_names<T: Into<trc::Value>>(names: impl Iterator<Item = T>) -> trc::Value {
|
||||||
|
trc::Value::Array(names.map(Into::into).collect())
|
||||||
|
}
|
||||||
|
|
||||||
/// A protocol a mail app signs in over, which the switch refuses (LP-6).
|
/// A protocol a mail app signs in over, which the switch refuses (LP-6).
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum LegacyProtocol {
|
pub enum LegacyProtocol {
|
||||||
@@ -302,6 +344,27 @@ impl Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The services mail apps sign in to, which the switch turns off: nothing may
|
||||||
|
/// offer them while it is (LP-7). SMTP here is submission -- mail apps
|
||||||
|
/// sending -- since inbound mail is never a configured service.
|
||||||
|
pub fn is_legacy_service(protocol: &ServiceProtocol) -> bool {
|
||||||
|
matches!(
|
||||||
|
protocol,
|
||||||
|
ServiceProtocol::Imap
|
||||||
|
| ServiceProtocol::Pop3
|
||||||
|
| ServiceProtocol::Smtp
|
||||||
|
| ServiceProtocol::Managesieve
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Server {
|
||||||
|
/// Whether the server-wide switch is off, for the answers that must stop
|
||||||
|
/// offering legacy services (LP-7). Read per answer, as sign-in reads it.
|
||||||
|
pub async fn legacy_protocols_off(&self) -> trc::Result<bool> {
|
||||||
|
Ok(self.protocol_policy().await?.legacy_protocols.is_disabled())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -349,6 +412,26 @@ mod tests {
|
|||||||
assert_eq!(err.value_as_str(trc::Key::AccountName), None);
|
assert_eq!(err.value_as_str(trc::Key::AccountName), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn only_the_services_mail_apps_sign_in_to_are_legacy() {
|
||||||
|
for protocol in [
|
||||||
|
ServiceProtocol::Imap,
|
||||||
|
ServiceProtocol::Pop3,
|
||||||
|
ServiceProtocol::Smtp,
|
||||||
|
ServiceProtocol::Managesieve,
|
||||||
|
] {
|
||||||
|
assert!(is_legacy_service(&protocol), "{protocol:?}");
|
||||||
|
}
|
||||||
|
for protocol in [
|
||||||
|
ServiceProtocol::Jmap,
|
||||||
|
ServiceProtocol::Caldav,
|
||||||
|
ServiceProtocol::Carddav,
|
||||||
|
ServiceProtocol::Webdav,
|
||||||
|
] {
|
||||||
|
assert!(!is_legacy_service(&protocol), "{protocol:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_domain_comes_from_the_name_given() {
|
fn the_domain_comes_from_the_name_given() {
|
||||||
assert_eq!(domain_of(&basic("[email protected]")), Some("b.test".to_string()));
|
assert_eq!(domain_of(&basic("[email protected]")), Some("b.test".to_string()));
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||||
|
*
|
||||||
|
* Modified by Coffey Labs in 2026 for INBUXA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::blob::UploadResponse;
|
use crate::blob::UploadResponse;
|
||||||
@@ -186,7 +188,10 @@ impl ToRequestError for trc::Error {
|
|||||||
trc::SecurityEvent::Unauthorized | trc::SecurityEvent::IpUnauthorized => {
|
trc::SecurityEvent::Unauthorized | trc::SecurityEvent::IpUnauthorized => {
|
||||||
RequestError::forbidden()
|
RequestError::forbidden()
|
||||||
}
|
}
|
||||||
trc::SecurityEvent::IpBlockExpired | trc::SecurityEvent::IpAllowExpired => {
|
// inbuxa: legacy-protocols LP-8 is an event, never an error
|
||||||
|
trc::SecurityEvent::IpBlockExpired
|
||||||
|
| trc::SecurityEvent::IpAllowExpired
|
||||||
|
| trc::SecurityEvent::LegacyProtocolsChanged => {
|
||||||
RequestError::internal_server_error()
|
RequestError::internal_server_error()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,8 +9,9 @@
|
|||||||
// This file is auto-generated. Do not edit directly.
|
// This file is auto-generated. Do not edit directly.
|
||||||
|
|
||||||
// inbuxa: 637 to 641 are the fork's SCIM events (SCIM-54); 642 is
|
// inbuxa: 637 to 641 are the fork's SCIM events (SCIM-54); 642 is
|
||||||
// auth.legacy-protocol-refused (legacy-protocols LP-6)
|
// auth.legacy-protocol-refused (legacy-protocols LP-6); 643 is
|
||||||
pub const TOTAL_EVENT_COUNT: usize = 643;
|
// security.legacy-protocols-changed (LP-8)
|
||||||
|
pub const TOTAL_EVENT_COUNT: usize = 644;
|
||||||
pub const TOTAL_METRIC_COUNT: usize = 369;
|
pub const TOTAL_METRIC_COUNT: usize = 369;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
@@ -655,6 +656,8 @@ pub enum SecurityEvent {
|
|||||||
IpAllowExpired = 594,
|
IpAllowExpired = 594,
|
||||||
IpUnauthorized = 279,
|
IpUnauthorized = 279,
|
||||||
Unauthorized = 552,
|
Unauthorized = 552,
|
||||||
|
// inbuxa: legacy-protocols LP-8
|
||||||
|
LegacyProtocolsChanged = 643,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
|||||||
@@ -446,6 +446,8 @@ impl EventType {
|
|||||||
b"security.ip-allow-expired" => EventType::Security(SecurityEvent::IpAllowExpired),
|
b"security.ip-allow-expired" => EventType::Security(SecurityEvent::IpAllowExpired),
|
||||||
b"security.ip-unauthorized" => EventType::Security(SecurityEvent::IpUnauthorized),
|
b"security.ip-unauthorized" => EventType::Security(SecurityEvent::IpUnauthorized),
|
||||||
b"security.unauthorized" => EventType::Security(SecurityEvent::Unauthorized),
|
b"security.unauthorized" => EventType::Security(SecurityEvent::Unauthorized),
|
||||||
|
// inbuxa: legacy-protocols LP-8
|
||||||
|
b"security.legacy-protocols-changed" => EventType::Security(SecurityEvent::LegacyProtocolsChanged),
|
||||||
b"server.startup" => EventType::Server(ServerEvent::Startup),
|
b"server.startup" => EventType::Server(ServerEvent::Startup),
|
||||||
b"server.shutdown" => EventType::Server(ServerEvent::Shutdown),
|
b"server.shutdown" => EventType::Server(ServerEvent::Shutdown),
|
||||||
b"server.startup-error" => EventType::Server(ServerEvent::StartupError),
|
b"server.startup-error" => EventType::Server(ServerEvent::StartupError),
|
||||||
@@ -1211,6 +1213,10 @@ impl EventType {
|
|||||||
EventType::Security(SecurityEvent::IpAllowExpired) => "security.ip-allow-expired",
|
EventType::Security(SecurityEvent::IpAllowExpired) => "security.ip-allow-expired",
|
||||||
EventType::Security(SecurityEvent::IpUnauthorized) => "security.ip-unauthorized",
|
EventType::Security(SecurityEvent::IpUnauthorized) => "security.ip-unauthorized",
|
||||||
EventType::Security(SecurityEvent::Unauthorized) => "security.unauthorized",
|
EventType::Security(SecurityEvent::Unauthorized) => "security.unauthorized",
|
||||||
|
// inbuxa: legacy-protocols LP-8
|
||||||
|
EventType::Security(SecurityEvent::LegacyProtocolsChanged) => {
|
||||||
|
"security.legacy-protocols-changed"
|
||||||
|
}
|
||||||
EventType::Server(ServerEvent::Startup) => "server.startup",
|
EventType::Server(ServerEvent::Startup) => "server.startup",
|
||||||
EventType::Server(ServerEvent::Shutdown) => "server.shutdown",
|
EventType::Server(ServerEvent::Shutdown) => "server.shutdown",
|
||||||
EventType::Server(ServerEvent::StartupError) => "server.startup-error",
|
EventType::Server(ServerEvent::StartupError) => "server.startup-error",
|
||||||
@@ -1883,6 +1889,8 @@ impl EventType {
|
|||||||
EventType::Security(SecurityEvent::IpAllowExpired) => 594,
|
EventType::Security(SecurityEvent::IpAllowExpired) => 594,
|
||||||
EventType::Security(SecurityEvent::IpUnauthorized) => 279,
|
EventType::Security(SecurityEvent::IpUnauthorized) => 279,
|
||||||
EventType::Security(SecurityEvent::Unauthorized) => 552,
|
EventType::Security(SecurityEvent::Unauthorized) => 552,
|
||||||
|
// inbuxa: legacy-protocols LP-8
|
||||||
|
EventType::Security(SecurityEvent::LegacyProtocolsChanged) => 643,
|
||||||
EventType::Server(ServerEvent::Startup) => 393,
|
EventType::Server(ServerEvent::Startup) => 393,
|
||||||
EventType::Server(ServerEvent::Shutdown) => 392,
|
EventType::Server(ServerEvent::Shutdown) => 392,
|
||||||
EventType::Server(ServerEvent::StartupError) => 394,
|
EventType::Server(ServerEvent::StartupError) => 394,
|
||||||
@@ -2571,6 +2579,8 @@ impl EventType {
|
|||||||
594 => Some(EventType::Security(SecurityEvent::IpAllowExpired)),
|
594 => Some(EventType::Security(SecurityEvent::IpAllowExpired)),
|
||||||
279 => Some(EventType::Security(SecurityEvent::IpUnauthorized)),
|
279 => Some(EventType::Security(SecurityEvent::IpUnauthorized)),
|
||||||
552 => Some(EventType::Security(SecurityEvent::Unauthorized)),
|
552 => Some(EventType::Security(SecurityEvent::Unauthorized)),
|
||||||
|
// inbuxa: legacy-protocols LP-8
|
||||||
|
643 => Some(EventType::Security(SecurityEvent::LegacyProtocolsChanged)),
|
||||||
393 => Some(EventType::Server(ServerEvent::Startup)),
|
393 => Some(EventType::Server(ServerEvent::Startup)),
|
||||||
392 => Some(EventType::Server(ServerEvent::Shutdown)),
|
392 => Some(EventType::Server(ServerEvent::Shutdown)),
|
||||||
394 => Some(EventType::Server(ServerEvent::StartupError)),
|
394 => Some(EventType::Server(ServerEvent::StartupError)),
|
||||||
@@ -2990,6 +3000,8 @@ impl EventType {
|
|||||||
EventType::Security(SecurityEvent::IpAllowExpired) => Level::Info,
|
EventType::Security(SecurityEvent::IpAllowExpired) => Level::Info,
|
||||||
EventType::Security(SecurityEvent::IpUnauthorized) => Level::Info,
|
EventType::Security(SecurityEvent::IpUnauthorized) => Level::Info,
|
||||||
EventType::Security(SecurityEvent::Unauthorized) => Level::Info,
|
EventType::Security(SecurityEvent::Unauthorized) => Level::Info,
|
||||||
|
// inbuxa: legacy-protocols LP-8
|
||||||
|
EventType::Security(SecurityEvent::LegacyProtocolsChanged) => Level::Info,
|
||||||
EventType::Server(ServerEvent::Startup) => Level::Info,
|
EventType::Server(ServerEvent::Startup) => Level::Info,
|
||||||
EventType::Server(ServerEvent::Shutdown) => Level::Info,
|
EventType::Server(ServerEvent::Shutdown) => Level::Info,
|
||||||
EventType::Server(ServerEvent::Licensing) => Level::Info,
|
EventType::Server(ServerEvent::Licensing) => Level::Info,
|
||||||
@@ -3198,7 +3210,9 @@ impl EventType {
|
|||||||
EventType::Auth(AuthEvent::TooManyAttempts) => "Too many authentication attempts",
|
EventType::Auth(AuthEvent::TooManyAttempts) => "Too many authentication attempts",
|
||||||
EventType::Auth(AuthEvent::ClientRegistration) => "OAuth Client registration",
|
EventType::Auth(AuthEvent::ClientRegistration) => "OAuth Client registration",
|
||||||
// inbuxa: legacy-protocols LP-6
|
// inbuxa: legacy-protocols LP-6
|
||||||
EventType::Auth(AuthEvent::LegacyProtocolRefused) => "Legacy mail protocol sign-in refused",
|
EventType::Auth(AuthEvent::LegacyProtocolRefused) => {
|
||||||
|
"Legacy mail protocol sign-in refused"
|
||||||
|
}
|
||||||
EventType::Auth(AuthEvent::Error) => "Authentication error",
|
EventType::Auth(AuthEvent::Error) => "Authentication error",
|
||||||
EventType::Auth(AuthEvent::Warning) => "Authentication warning",
|
EventType::Auth(AuthEvent::Warning) => "Authentication warning",
|
||||||
EventType::Auth(AuthEvent::CredentialExpired) => "Credential expired",
|
EventType::Auth(AuthEvent::CredentialExpired) => "Credential expired",
|
||||||
@@ -3711,6 +3725,10 @@ impl EventType {
|
|||||||
EventType::Security(SecurityEvent::IpAllowExpired) => "IP allow expired",
|
EventType::Security(SecurityEvent::IpAllowExpired) => "IP allow expired",
|
||||||
EventType::Security(SecurityEvent::IpUnauthorized) => "Unauthorized IP address",
|
EventType::Security(SecurityEvent::IpUnauthorized) => "Unauthorized IP address",
|
||||||
EventType::Security(SecurityEvent::Unauthorized) => "Unauthorized access",
|
EventType::Security(SecurityEvent::Unauthorized) => "Unauthorized access",
|
||||||
|
// inbuxa: legacy-protocols LP-8
|
||||||
|
EventType::Security(SecurityEvent::LegacyProtocolsChanged) => {
|
||||||
|
"Legacy mail protocols switch changed"
|
||||||
|
}
|
||||||
EventType::Server(ServerEvent::Startup) => "Starting INBUXA Server",
|
EventType::Server(ServerEvent::Startup) => "Starting INBUXA Server",
|
||||||
EventType::Server(ServerEvent::Shutdown) => "Shutting down INBUXA Server",
|
EventType::Server(ServerEvent::Shutdown) => "Shutting down INBUXA Server",
|
||||||
EventType::Server(ServerEvent::StartupError) => "Server startup error",
|
EventType::Server(ServerEvent::StartupError) => "Server startup error",
|
||||||
@@ -3964,7 +3982,9 @@ impl EventType {
|
|||||||
EventType::Auth(AuthEvent::TooManyAttempts) => "Too many authentication attempts",
|
EventType::Auth(AuthEvent::TooManyAttempts) => "Too many authentication attempts",
|
||||||
EventType::Auth(AuthEvent::ClientRegistration) => "Authentication error",
|
EventType::Auth(AuthEvent::ClientRegistration) => "Authentication error",
|
||||||
// inbuxa: legacy-protocols LP-6
|
// inbuxa: legacy-protocols LP-6
|
||||||
EventType::Auth(AuthEvent::LegacyProtocolRefused) => "This server allows only INBUXA webmail and JMAP apps",
|
EventType::Auth(AuthEvent::LegacyProtocolRefused) => {
|
||||||
|
"This server allows only INBUXA webmail and JMAP apps"
|
||||||
|
}
|
||||||
EventType::Auth(AuthEvent::Error) => "Authentication error",
|
EventType::Auth(AuthEvent::Error) => "Authentication error",
|
||||||
EventType::Auth(AuthEvent::CredentialExpired) => "Credential expired",
|
EventType::Auth(AuthEvent::CredentialExpired) => "Credential expired",
|
||||||
EventType::Imap(ImapEvent::ConnectionStart) => "IMAP error",
|
EventType::Imap(ImapEvent::ConnectionStart) => "IMAP error",
|
||||||
@@ -4102,6 +4122,10 @@ impl EventType {
|
|||||||
EventType::Security(SecurityEvent::IpAllowExpired) => "Insufficient permissions",
|
EventType::Security(SecurityEvent::IpAllowExpired) => "Insufficient permissions",
|
||||||
EventType::Security(SecurityEvent::IpUnauthorized) => "Unauthorized IP address",
|
EventType::Security(SecurityEvent::IpUnauthorized) => "Unauthorized IP address",
|
||||||
EventType::Security(SecurityEvent::Unauthorized) => "Insufficient permissions",
|
EventType::Security(SecurityEvent::Unauthorized) => "Insufficient permissions",
|
||||||
|
// inbuxa: legacy-protocols LP-8
|
||||||
|
EventType::Security(SecurityEvent::LegacyProtocolsChanged) => {
|
||||||
|
"Legacy mail protocols switch changed"
|
||||||
|
}
|
||||||
EventType::Smtp(SmtpEvent::ConnectionStart) => "SMTP error",
|
EventType::Smtp(SmtpEvent::ConnectionStart) => "SMTP error",
|
||||||
EventType::Smtp(SmtpEvent::ConnectionEnd) => "SMTP error",
|
EventType::Smtp(SmtpEvent::ConnectionEnd) => "SMTP error",
|
||||||
EventType::Smtp(SmtpEvent::Error) => "SMTP error",
|
EventType::Smtp(SmtpEvent::Error) => "SMTP error",
|
||||||
@@ -4663,6 +4687,8 @@ impl EventType {
|
|||||||
EventType::Security(SecurityEvent::IpAllowExpired),
|
EventType::Security(SecurityEvent::IpAllowExpired),
|
||||||
EventType::Security(SecurityEvent::IpUnauthorized),
|
EventType::Security(SecurityEvent::IpUnauthorized),
|
||||||
EventType::Security(SecurityEvent::Unauthorized),
|
EventType::Security(SecurityEvent::Unauthorized),
|
||||||
|
// inbuxa: legacy-protocols LP-8
|
||||||
|
EventType::Security(SecurityEvent::LegacyProtocolsChanged),
|
||||||
EventType::Server(ServerEvent::Startup),
|
EventType::Server(ServerEvent::Startup),
|
||||||
EventType::Server(ServerEvent::Shutdown),
|
EventType::Server(ServerEvent::Shutdown),
|
||||||
EventType::Server(ServerEvent::StartupError),
|
EventType::Server(ServerEvent::StartupError),
|
||||||
|
|||||||
Binary file not shown.
@@ -1 +1 @@
|
|||||||
q-OZe-InKnF24mlL56Vvt3m_IQNRybiN61MFxBSo0WY
|
rLRbZKj15KvcPMVmnisfCDsXXZEKks6BXpMkMpS0mlI
|
||||||
@@ -17,7 +17,11 @@ submission -- locked open -- is refused with the spec's words, with the right
|
|||||||
password and with a wrong one, and refusals never add up to a disconnect
|
password and with a wrong one, and refusals never add up to a disconnect
|
||||||
(LP-11). And that a normal IMAP sign-in works with the switch on, before and
|
(LP-11). And that a normal IMAP sign-in works with the switch on, before and
|
||||||
after. And that while it is off, no listener the switch would close can be
|
after. And that while it is off, no listener the switch would close can be
|
||||||
created, or made by an update (LP-4, test 4).
|
created, or made by an update (LP-4, test 4), and nothing advertises what is
|
||||||
|
closed: autoconfig, autodiscover and PACC offer no IMAP, POP3 or submission,
|
||||||
|
and the suggested zone marks their SRV names not offered (LP-7, test 5).
|
||||||
|
Every change of the switch, and every refused sign-in, is an event in the
|
||||||
|
server's log (LP-8, test 14; LP-6).
|
||||||
|
|
||||||
Passwords are generated into files under target/e2e and never printed.
|
Passwords are generated into files under target/e2e and never printed.
|
||||||
Everything is removed afterwards unless KEEP=1.
|
Everything is removed afterwards unless KEEP=1.
|
||||||
@@ -189,6 +193,47 @@ def smtp_auths(port, user, passwords):
|
|||||||
return replies
|
return replies
|
||||||
|
|
||||||
|
|
||||||
|
def advertised(admin, admin_pw):
|
||||||
|
"""What each client-configuration answer and the suggested zone offer."""
|
||||||
|
with urllib.request.urlopen(f"{HTTP}/mail/[email protected]",
|
||||||
|
timeout=30) as resp:
|
||||||
|
autoconfig = resp.read().decode()
|
||||||
|
body = ('<?xml version="1.0" encoding="utf-8"?><Autodiscover xmlns="http://schemas.'
|
||||||
|
'microsoft.com/exchange/autodiscover/outlook/requestschema/2006"><Request>'
|
||||||
|
'<EMailAddress>[email protected]</EMailAddress><AcceptableResponseSchema>http://'
|
||||||
|
'schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a'
|
||||||
|
'</AcceptableResponseSchema></Request></Autodiscover>').encode()
|
||||||
|
req = urllib.request.Request(f"{HTTP}/autodiscover/autodiscover.xml", data=body, method="POST")
|
||||||
|
req.add_header("Content-Type", "text/xml")
|
||||||
|
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||||
|
autodiscover = resp.read().decode()
|
||||||
|
with urllib.request.urlopen(f"{HTTP}/.well-known/user-agent-configuration.json",
|
||||||
|
timeout=30) as resp:
|
||||||
|
pacc = json.load(resp).get("protocols", {})
|
||||||
|
got = one(admin, admin_pw, "x:Domain/get", {"ids": None, "properties": ["name", "dnsZoneFile"]})
|
||||||
|
zone = next((d.get("dnsZoneFile") or "" for d in got[1].get("list", [])
|
||||||
|
if d.get("name") == "legacy.test"), "")
|
||||||
|
srv = {}
|
||||||
|
for line in zone.splitlines():
|
||||||
|
fields = line.split()
|
||||||
|
if "SRV" in fields and fields[0].startswith("_"):
|
||||||
|
srv[fields[0].split(".")[0] + "." + fields[0].split(".")[1]] = fields[-1]
|
||||||
|
return {
|
||||||
|
"autoconfig": {t for t in ("imap", "pop3", "smtp") if f'type="{t}"' in autoconfig},
|
||||||
|
"autodiscover": {t for t in ("IMAP", "POP3", "SMTP") if f"<Type>{t}</Type>" in autodiscover},
|
||||||
|
"pacc": {t for t in ("imap", "pop3", "smtp", "managesieve") if t in pacc},
|
||||||
|
"jmap": "jmap" in pacc,
|
||||||
|
"srv": srv,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def events(name):
|
||||||
|
"""The server's log lines for one event, from its stdout tracer. The log
|
||||||
|
is the container's, so it starts afresh at every restart."""
|
||||||
|
out = docker("logs", NAME, check_rc=False)
|
||||||
|
return [l for l in (out.stdout + out.stderr).splitlines() if f"({name})" in l]
|
||||||
|
|
||||||
|
|
||||||
def settle(port, want, tries=30):
|
def settle(port, want, tries=30):
|
||||||
"""Wait for a port to reach the wanted state, so the check is not a race."""
|
"""Wait for a port to reach the wanted state, so the check is not a race."""
|
||||||
for _ in range(tries):
|
for _ in range(tries):
|
||||||
@@ -232,6 +277,15 @@ def main():
|
|||||||
stop()
|
stop()
|
||||||
start()
|
start()
|
||||||
|
|
||||||
|
# A tracer to stdout, so the events can be read back from the container's
|
||||||
|
# log. It takes effect from the next start.
|
||||||
|
res = one(admin, admin_pw, "x:Tracer/set", {"create": {"t": {
|
||||||
|
"@type": "Stdout", "level": "info", "buffered": False, "ansi": False}}})
|
||||||
|
if not (res[1].get("created") or {}).get("t"):
|
||||||
|
sys.exit("tracer create failed: " + json.dumps(res))
|
||||||
|
stop()
|
||||||
|
start()
|
||||||
|
|
||||||
sess = session(admin, admin_pw)
|
sess = session(admin, admin_pw)
|
||||||
account = sess["primaryAccounts"].get(INBUXA) or list(sess["accounts"])[0]
|
account = sess["primaryAccounts"].get(INBUXA) or list(sess["accounts"])[0]
|
||||||
policy_get = {"accountId": account, "ids": None}
|
policy_get = {"accountId": account, "ids": None}
|
||||||
@@ -243,6 +297,15 @@ def main():
|
|||||||
check(accepts(PORTS["submissions"]), "submission accepts before the switch")
|
check(accepts(PORTS["submissions"]), "submission accepts before the switch")
|
||||||
check(accepts(PORTS["smtp"]), "inbound SMTP accepts before the switch")
|
check(accepts(PORTS["smtp"]), "inbound SMTP accepts before the switch")
|
||||||
|
|
||||||
|
# What is advertised with the switch on -- the control for LP-7.
|
||||||
|
before = advertised(admin, admin_pw)
|
||||||
|
print(" advertised before:", {k: sorted(v) if isinstance(v, set) else v
|
||||||
|
for k, v in before.items() if k != "srv"})
|
||||||
|
check(before["autoconfig"] and before["autodiscover"],
|
||||||
|
"autoconfig and autodiscover offer mail apps a server with the switch on")
|
||||||
|
check(before["srv"].get("_imaps._tcp", ".") != ".",
|
||||||
|
"the suggested zone offers IMAP with the switch on")
|
||||||
|
|
||||||
# A normal sign-in works with the switch on -- the control for LP-6.
|
# A normal sign-in works with the switch on -- the control for LP-6.
|
||||||
check(imap_login(PORTS["imap"], admin, admin_pw).startswith("OK"),
|
check(imap_login(PORTS["imap"], admin, admin_pw).startswith("OK"),
|
||||||
"IMAP sign-in works with the switch on")
|
"IMAP sign-in works with the switch on")
|
||||||
@@ -295,6 +358,19 @@ def main():
|
|||||||
if not all(r == SMTP_REFUSAL for r in replies):
|
if not all(r == SMTP_REFUSAL for r in replies):
|
||||||
print(" replies:", replies)
|
print(" replies:", replies)
|
||||||
|
|
||||||
|
# Nothing advertises what is closed (LP-7, test 5).
|
||||||
|
during = advertised(admin, admin_pw)
|
||||||
|
check(not during["autoconfig"], "autoconfig offers no IMAP, POP3 or submission (LP-7)")
|
||||||
|
check(not during["autodiscover"], "autodiscover offers no IMAP, POP3 or submission (LP-7)")
|
||||||
|
check(not during["pacc"] and during["jmap"], "PACC offers JMAP and nothing legacy (LP-7)")
|
||||||
|
names = ("_imap._tcp", "_imaps._tcp", "_pop3._tcp", "_pop3s._tcp",
|
||||||
|
"_submission._tcp", "_submissions._tcp")
|
||||||
|
offered = {n: t for n, t in during["srv"].items() if n in names and t != "."}
|
||||||
|
check(not offered and "_imaps._tcp" in during["srv"],
|
||||||
|
"the suggested zone marks the legacy SRV names not offered, target . (LP-7)")
|
||||||
|
if offered or "_imaps._tcp" not in during["srv"]:
|
||||||
|
print(" srv:", during["srv"])
|
||||||
|
|
||||||
# No listener the switch would close can be added while it is off (LP-4,
|
# No listener the switch would close can be added while it is off (LP-4,
|
||||||
# test 4), and the refusal names the policy.
|
# test 4), and the refusal names the policy.
|
||||||
res = one(admin, admin_pw, "x:NetworkListener/set", {"create": {"m": {
|
res = one(admin, admin_pw, "x:NetworkListener/set", {"create": {"m": {
|
||||||
@@ -322,6 +398,25 @@ def main():
|
|||||||
"turning it into an IMAP listener is refused (LP-4)")
|
"turning it into an IMAP listener is refused (LP-4)")
|
||||||
one(admin, admin_pw, "x:NetworkListener/set", {"destroy": [extra]})
|
one(admin, admin_pw, "x:NetworkListener/set", {"destroy": [extra]})
|
||||||
|
|
||||||
|
# The change was reported (LP-8, test 14), with who made it and what closed.
|
||||||
|
changed = events("security.legacy-protocols-changed")
|
||||||
|
check(len(changed) == 1 and 'value = "disabled"' in changed[0]
|
||||||
|
and 'policy = "server"' in changed[0] and 'details = "closed"' in changed[0]
|
||||||
|
and '"imaps"' in changed[0] and "accountId = " in changed[0],
|
||||||
|
"turning it off is one event: scope, new value, who, listeners closed (LP-8)")
|
||||||
|
if len(changed) != 1:
|
||||||
|
print(" events:", changed)
|
||||||
|
# Asking for what already holds is not a change.
|
||||||
|
one(admin, admin_pw, "inbuxa:ProtocolPolicy/set", policy_set({"legacyProtocols": "disabled"}))
|
||||||
|
check(len(events("security.legacy-protocols-changed")) == 1,
|
||||||
|
"setting it off again when it is off raises no event (LP-8)")
|
||||||
|
# Every refused sign-in is an event too, and none is a failed sign-in.
|
||||||
|
refused = events("auth.legacy-protocol-refused")
|
||||||
|
check(len(refused) == 7 and all('source = "submission"' in l for l in refused),
|
||||||
|
"each refused sign-in is an auth.legacy-protocol-refused event (LP-6)")
|
||||||
|
check(not events("auth.failed") and not events("auth.too-many-attempts"),
|
||||||
|
"and none is logged as a failed sign-in (LP-11)")
|
||||||
|
|
||||||
# A restart must not reopen them: the objects are gone, not just the sockets.
|
# A restart must not reopen them: the objects are gone, not just the sockets.
|
||||||
stop()
|
stop()
|
||||||
start()
|
start()
|
||||||
@@ -340,6 +435,14 @@ def main():
|
|||||||
policy = got[1]["list"][0]
|
policy = got[1]["list"][0]
|
||||||
check(policy["legacyProtocols"] == "enabled", "switch reads back enabled")
|
check(policy["legacyProtocols"] == "enabled", "switch reads back enabled")
|
||||||
check(not policy["savedListeners"], "savedListeners is empty again (LP-5)")
|
check(not policy["savedListeners"], "savedListeners is empty again (LP-5)")
|
||||||
|
changed = events("security.legacy-protocols-changed")
|
||||||
|
check(len(changed) == 1 and 'value = "enabled"' in changed[0]
|
||||||
|
and 'details = "reopened"' in changed[0] and '"imaps"' in changed[0],
|
||||||
|
"turning it back on is one event, naming the listeners reopened (LP-8)")
|
||||||
|
|
||||||
|
after = advertised(admin, admin_pw)
|
||||||
|
check(after["autoconfig"] == before["autoconfig"] and after["srv"] == before["srv"],
|
||||||
|
"autoconfig and the suggested zone offer them again once back on")
|
||||||
|
|
||||||
# And sign-in works again, with no restart.
|
# And sign-in works again, with no restart.
|
||||||
check(imap_login(PORTS["imap"], admin, admin_pw).startswith("OK"),
|
check(imap_login(PORTS["imap"], admin, admin_pw).startswith("OK"),
|
||||||
|
|||||||
Reference in New Issue
Block a user