Queue autoconfig post install

This commit is contained in:
mdecimus
2025-07-13 12:01:32 +02:00
parent 036cb2ecd4
commit aee7337169
21 changed files with 302 additions and 280 deletions

View File

@@ -34,6 +34,7 @@ use utils::config::{Config, utils::ParseValue};
rkyv::Archive,
serde::Deserialize,
)]
#[rkyv(derive(Debug, Clone, Copy, PartialEq), compare(PartialEq))]
#[repr(transparent)]
pub struct QueueName([u8; 8]);
@@ -433,13 +434,13 @@ fn parse_route(config: &mut Config, id: &str) -> Option<RoutingStrategy> {
"local" => RoutingStrategy::Local.into(),
"mx" => RoutingStrategy::Mx(MxConfig {
max_mx: config
.property_require(("queue.route", id, "limits.mx"))
.property(("queue.route", id, "limits.mx"))
.unwrap_or(5),
max_multi_homed: config
.property_require(("queue.route", id, "limits.multihomed"))
.property(("queue.route", id, "limits.multihomed"))
.unwrap_or(2),
ip_lookup_strategy: config
.property_require(("queue.route", id, "ip-lookup"))
.property(("queue.route", id, "ip-lookup"))
.unwrap_or(IpLookupStrategy::Ipv4thenIpv6),
})
.into(),
@@ -475,22 +476,22 @@ fn parse_tls_strategies(config: &mut Config) -> AHashMap<String, TlsStrategy> {
fn parse_tls(config: &mut Config, id: &str) -> Option<TlsStrategy> {
Some(TlsStrategy {
dane: config
.property_require::<RequireOptional>(("queue.tls", id, "dane"))
.property::<RequireOptional>(("queue.tls", id, "dane"))
.unwrap_or(RequireOptional::Optional),
mta_sts: config
.property_require::<RequireOptional>(("queue.tls", id, "mta-sts"))
.property::<RequireOptional>(("queue.tls", id, "mta-sts"))
.unwrap_or(RequireOptional::Optional),
tls: config
.property_require::<RequireOptional>(("queue.tls", id, "starttls"))
.property::<RequireOptional>(("queue.tls", id, "starttls"))
.unwrap_or(RequireOptional::Optional),
allow_invalid_certs: config
.property_require::<bool>(("queue.tls", id, "allow-invalid-certs"))
.property::<bool>(("queue.tls", id, "allow-invalid-certs"))
.unwrap_or(false),
timeout_tls: config
.property_require::<Duration>(("queue.tls", id, "timeout.tls"))
.property::<Duration>(("queue.tls", id, "timeout.tls"))
.unwrap_or(Duration::from_secs(3 * 60)),
timeout_mta_sts: config
.property_require::<Duration>(("queue.tls", id, "timeout.mta-sts"))
.property::<Duration>(("queue.tls", id, "timeout.mta-sts"))
.unwrap_or(Duration::from_secs(5 * 60)),
})
}
@@ -538,22 +539,22 @@ fn parse_connection(config: &mut Config, id: &str) -> Option<ConnectionStrategy>
source_ipv6,
ehlo_hostname: config.property::<String>(("queue.connection", id, "ehlo-hostname")),
timeout_connect: config
.property_require::<Duration>(("queue.connection", id, "timeout.connect"))
.property::<Duration>(("queue.connection", id, "timeout.connect"))
.unwrap_or(Duration::from_secs(5 * 60)),
timeout_greeting: config
.property_require::<Duration>(("queue.connection", id, "timeout.greeting"))
.property::<Duration>(("queue.connection", id, "timeout.greeting"))
.unwrap_or(Duration::from_secs(5 * 60)),
timeout_ehlo: config
.property_require::<Duration>(("queue.connection", id, "timeout.ehlo"))
.property::<Duration>(("queue.connection", id, "timeout.ehlo"))
.unwrap_or(Duration::from_secs(5 * 60)),
timeout_mail: config
.property_require::<Duration>(("queue.connection", id, "timeout.mail-from"))
.property::<Duration>(("queue.connection", id, "timeout.mail-from"))
.unwrap_or(Duration::from_secs(5 * 60)),
timeout_rcpt: config
.property_require::<Duration>(("queue.connection", id, "timeout.rcpt-to"))
.property::<Duration>(("queue.connection", id, "timeout.rcpt-to"))
.unwrap_or(Duration::from_secs(5 * 60)),
timeout_data: config
.property_require::<Duration>(("queue.connection", id, "timeout.data"))
.property::<Duration>(("queue.connection", id, "timeout.data"))
.unwrap_or(Duration::from_secs(10 * 60)),
})
}
@@ -935,6 +936,14 @@ impl QueueName {
}
}
impl ArchivedQueueName {
pub fn as_str(&self) -> &str {
std::str::from_utf8(self.0.as_ref())
.unwrap_or_default()
.trim_end_matches('\0')
}
}
impl Default for QueueName {
fn default() -> Self {
DEFAULT_QUEUE_NAME
@@ -955,7 +964,13 @@ impl ParseValue for QueueName {
impl Display for QueueName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
self.as_str().fmt(f)
}
}
impl Display for ArchivedQueueName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}

View File

@@ -367,8 +367,14 @@ impl TokenMap {
V_QUEUE_EXPIRES_IN,
V_QUEUE_LAST_STATUS,
V_QUEUE_LAST_ERROR,
V_QUEUE_NAME,
V_QUEUE_AGE,
V_ASN,
V_COUNTRY,
V_RECEIVED_FROM_IP,
V_RECEIVED_VIA_PORT,
V_SOURCE,
V_SIZE,
])
}

View File

@@ -29,7 +29,7 @@ use store::{
};
use tokio::sync::{Notify, mpsc};
use utils::{
Semver, UnwrapFailure,
UnwrapFailure,
config::{Config, ConfigKey},
failed,
};
@@ -75,6 +75,106 @@ enum StoreOp {
None,
}
pub const DEFAULT_SETTINGS: &[(&str, &str)] = &[
("queue.quota.size.messages", "100000"),
("queue.quota.size.size", "10737418240"),
("queue.quota.size.enable", "true"),
("queue.limiter.inbound.ip.key", "remote_ip"),
("queue.limiter.inbound.ip.rate", "5/1s"),
("queue.limiter.inbound.ip.enable", "true"),
("queue.limiter.inbound.sender.key.0", "sender_domain"),
("queue.limiter.inbound.sender.key.1", "rcpt"),
("queue.limiter.inbound.sender.rate", "25/1h"),
("queue.limiter.inbound.sender.enable", "true"),
("report.analysis.addresses", "postmaster@*"),
("queue.virtual.local.threads-per-node", "25"),
("queue.virtual.local.description", "Local delivery queue"),
("queue.virtual.remote.threads-per-node", "50"),
("queue.virtual.remote.description", "Remote delivery queue"),
("queue.virtual.dsn.threads-per-node", "5"),
(
"queue.virtual.dsn.description",
"Delivery Status Notification delivery queue",
),
("queue.virtual.report.threads-per-node", "5"),
(
"queue.virtual.report.description",
"DMARC and TLS report delivery queue",
),
("queue.schedule.local.queue-name", "local"),
("queue.schedule.local.retry.0", "2m"),
("queue.schedule.local.retry.1", "5m"),
("queue.schedule.local.retry.2", "10m"),
("queue.schedule.local.retry.3", "15m"),
("queue.schedule.local.retry.4", "30m"),
("queue.schedule.local.retry.5", "1h"),
("queue.schedule.local.retry.6", "2h"),
("queue.schedule.local.notify.0", "1d"),
("queue.schedule.local.notify.1", "3d"),
("queue.schedule.local.expire-type", "ttl"),
("queue.schedule.local.expire", "3d"),
(
"queue.schedule.local.description",
"Local delivery schedule",
),
("queue.schedule.remote.queue-name", "remote"),
("queue.schedule.remote.retry.0", "2m"),
("queue.schedule.remote.retry.1", "5m"),
("queue.schedule.remote.retry.2", "10m"),
("queue.schedule.remote.retry.3", "15m"),
("queue.schedule.remote.retry.4", "30m"),
("queue.schedule.remote.retry.5", "1h"),
("queue.schedule.remote.retry.6", "2h"),
("queue.schedule.remote.notify.0", "1d"),
("queue.schedule.remote.notify.1", "3d"),
("queue.schedule.remote.expire-type", "ttl"),
("queue.schedule.remote.expire", "3d"),
(
"queue.schedule.remote.description",
"Remote delivery schedule",
),
("queue.schedule.dsn.queue-name", "dsn"),
("queue.schedule.dsn.retry.0", "15m"),
("queue.schedule.dsn.retry.1", "30m"),
("queue.schedule.dsn.retry.2", "1h"),
("queue.schedule.dsn.retry.3", "2h"),
("queue.schedule.dsn.expire-type", "attempts"),
("queue.schedule.dsn.max-attempts", "10"),
(
"queue.schedule.dsn.description",
"Delivery Status Notification delivery schedule",
),
("queue.schedule.report.queue-name", "report"),
("queue.schedule.report.retry.0", "30m"),
("queue.schedule.report.retry.1", "1h"),
("queue.schedule.report.retry.2", "2h"),
("queue.schedule.report.expire-type", "attempts"),
("queue.schedule.report.max-attempts", "8"),
(
"queue.schedule.report.description",
"DMARC and TLS report delivery schedule",
),
("queue.tls.invalid-tls.allow-invalid-certs", "true"),
(
"queue.tls.invalid-tls.description",
"Allow invalid TLS certificates",
),
("queue.tls.default.allow-invalid-certs", "false"),
("queue.tls.default.description", "Default TLS settings"),
("queue.route.local.type", "local"),
("queue.route.local.description", "Local delivery route"),
("queue.route.mx.type", "mx"),
("queue.route.mx.limits.multihomed", "2"),
("queue.route.mx.limits.mx", "5"),
("queue.route.mx.ip-lookup", "ipv4_then_ipv6"),
("queue.route.mx.description", "MX delivery route"),
("queue.connection.default.timeout.connect", "5m"),
(
"queue.connection.default.description",
"Default connection settings",
),
];
impl BootManager {
pub async fn init() -> Self {
let mut config_path = std::env::var("CONFIG_PATH").ok();
@@ -244,84 +344,29 @@ impl BootManager {
}
// Download Spam filter rules if missing
// TODO remove this check in 1.0
let update_webadmin = match config.value("version.spam-filter").and_then(|v| {
if !v.is_empty() {
Some(Semver::try_from(v))
} else {
None
}
}) {
Some(Err(_)) => {
let _ = manager.clear_prefix("lookup.spam-").await;
let _ = manager
.clear_prefix("sieve.trusted.scripts.spam-filter")
.await;
let _ = manager
.clear_prefix("sieve.trusted.scripts.track-replies")
.await;
let _ = manager.clear_prefix("sieve.trusted.scripts.greylist").await;
let _ = manager.clear_prefix("sieve.trusted.scripts.train").await;
let _ = manager.clear("version.spam-filter").await;
match manager.fetch_spam_rules().await {
Ok(external_config) => {
trc::event!(
Config(trc::ConfigEvent::ImportExternal),
Version = external_config.version.to_string(),
Id = "spam-filter"
);
insert_keys.extend(external_config.keys);
}
Err(err) => {
config.new_build_error(
"*",
format!("Failed to fetch spam filter: {err}"),
);
}
if config.value("version.spam-filter").is_none() {
match manager.fetch_spam_rules().await {
Ok(external_config) => {
trc::event!(
Config(trc::ConfigEvent::ImportExternal),
Version = external_config.version.to_string(),
Id = "spam-filter"
);
insert_keys.extend(external_config.keys);
}
true
}
Some(Ok(_)) => false,
None => {
match manager.fetch_spam_rules().await {
Ok(external_config) => {
trc::event!(
Config(trc::ConfigEvent::ImportExternal),
Version = external_config.version.to_string(),
Id = "spam-filter"
);
insert_keys.extend(external_config.keys);
}
Err(err) => {
config.new_build_error(
"*",
format!("Failed to fetch spam filter: {err}"),
);
}
Err(err) => {
config.new_build_error(
"*",
format!("Failed to fetch spam filter: {err}"),
);
}
// Add default settings
for key in [
("queue.quota.size.messages", "100000"),
("queue.quota.size.size", "10737418240"),
("queue.quota.size.enable", "true"),
("queue.limiter.inbound.ip.key", "remote_ip"),
("queue.limiter.inbound.ip.rate", "5/1s"),
("queue.limiter.inbound.ip.enable", "true"),
("queue.limiter.inbound.sender.key.0", "sender_domain"),
("queue.limiter.inbound.sender.key.1", "rcpt"),
("queue.limiter.inbound.sender.rate", "25/1h"),
("queue.limiter.inbound.sender.enable", "true"),
("report.analysis.addresses", "postmaster@*"),
] {
insert_keys.push(ConfigKey::from(key));
}
false
}
};
// Add default settings
for key in DEFAULT_SETTINGS {
insert_keys.push(ConfigKey::from(*key));
}
}
// Download webadmin if missing
if let Some(blob_store) = config
@@ -393,10 +438,9 @@ impl BootManager {
);
// Webadmin auto-update
if update_webadmin
|| config
.property_or_default::<bool>("webadmin.auto-update", "false")
.unwrap_or_default()
if config
.property_or_default::<bool>("webadmin.auto-update", "false")
.unwrap_or_default()
{
if let Err(err) = data.webadmin.update(&core).await {
trc::event!(