/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use crate::auth::permissions::DefaultPermissions; use aws_lc_rs::{ rand::SystemRandom, signature::{ECDSA_P256_SHA256_FIXED_SIGNING, EcdsaKeyPair}, }; use registry::{ schema::{ enums::*, prelude::{ObjectType, SocketAddr}, structs::*, }, types::{duration::Duration, error::Error, list::List, map::Map}, }; use std::str::FromStr; use store::{ rand::{Rng, distr::Alphanumeric, rng}, registry::{ bootstrap::Bootstrap, write::{RegistryWrite, RegistryWriteResult}, }, }; pub const ASN_IPV4: &str = "https://cdn.jsdelivr.net/npm/@ip-location-db/asn/asn-ipv4.csv"; pub const ASN_IPV6: &str = "https://cdn.jsdelivr.net/npm/@ip-location-db/asn/asn-ipv6.csv"; pub const GEO_IPV4: &str = "https://cdn.jsdelivr.net/npm/@ip-location-db/geolite2-geo-whois-asn-country/geolite2-geo-whois-asn-country-ipv4.csv"; pub const GEO_IPV6: &str = "https://cdn.jsdelivr.net/npm/@ip-location-db/geolite2-geo-whois-asn-country/geolite2-geo-whois-asn-country-ipv6.csv"; pub trait BootstrapDefaults { fn insert_safe_defaults(&mut self) -> impl Future + Send; } impl BootstrapDefaults for Bootstrap { async fn insert_safe_defaults(&mut self) { if let Err(error) = insert_safe_defaults(self).await { self.errors.push(Error::Internal { object_id: None, error, }); } } } async fn insert_safe_defaults(bp: &mut Bootstrap) -> trc::Result<()> { let is_recovery_mode = bp.registry.is_recovery_mode(); let is_bootstrap_mode = bp.registry.is_bootstrap_mode(); #[cfg(not(feature = "test_mode"))] if (!is_recovery_mode || is_bootstrap_mode) && bp.registry.count_object(ObjectType::Application).await? == 0 { bp.registry .write(RegistryWrite::insert( &Application { auto_update_frequency: Duration::from_millis(30 * 24 * 60 * 60 * 1000), description: "Stalwart Web Interface".to_string(), enabled: true, #[cfg(not(feature = "dev_mode"))] resource_url: "https://github.com/stalwartlabs/webui/releases/latest/download/webui.zip" .into(), #[cfg(feature = "dev_mode")] resource_url: "file:///Users/me/code/webui/.ignore/webui.zip".into(), unpack_directory: None, url_prefix: Map::new(vec!["/admin".into(), "/account".into()]), } .into(), )) .await?; } if is_bootstrap_mode { #[cfg(not(any(feature = "dev_mode", feature = "test_mode")))] if bp.registry.count_object(ObjectType::SystemSettings).await? == 0 { bp.registry .write(RegistryWrite::insert( &SystemSettings { default_hostname: bp.registry.local_hostname().to_string(), ..Default::default() } .into(), )) .await?; } return Ok(()); } if is_recovery_mode { return Ok(()); } if bp.registry.count_object(ObjectType::MtaQueueQuota).await? == 0 { bp.registry .write(RegistryWrite::insert( &MtaQueueQuota { description: "Global queue quota".to_string().into(), enable: true, messages: 100000.into(), size: 10737418240.into(), ..Default::default() } .into(), )) .await?; } if bp .registry .count_object(ObjectType::MtaInboundThrottle) .await? == 0 { for object in [ MtaInboundThrottle { description: "Sender IP throttle".to_string(), enable: true, key: Map::new(vec![MtaInboundThrottleKey::RemoteIp]), rate: Rate { count: 5, period: Duration::from_millis(1000), }, ..Default::default() }, MtaInboundThrottle { description: "Sender address to recipient throttle".to_string(), enable: true, key: Map::new(vec![ MtaInboundThrottleKey::SenderDomain, MtaInboundThrottleKey::Rcpt, ]), rate: Rate { count: 25, period: Duration::from_millis(60 * 60 * 1000), }, ..Default::default() }, ] { bp.registry .write(RegistryWrite::insert(&object.into())) .await?; } } if bp .registry .count_object(ObjectType::MtaVirtualQueue) .await? == 0 && bp .registry .count_object(ObjectType::MtaDeliverySchedule) .await? == 0 { for (id, object) in [ MtaVirtualQueue { description: "Local delivery queue".to_string().into(), name: "local".into(), threads_per_node: 25, }, MtaVirtualQueue { description: "Remote delivery queue".to_string().into(), name: "remote".into(), threads_per_node: 50, }, MtaVirtualQueue { description: "Delivery Status Notification delivery queue" .to_string() .into(), name: "dsn".into(), threads_per_node: 5, }, MtaVirtualQueue { description: "DMARC and TLS report delivery queue".to_string().into(), name: "report".into(), threads_per_node: 5, }, ] .into_iter() .enumerate() { bp.registry .write(RegistryWrite::insert_with_id( (id as u64).into(), &object.into(), )) .await?; } for (id, object) in [ MtaDeliverySchedule { name: "local".into(), description: "Local delivery schedule".to_string().into(), expiry: MtaDeliveryExpiration::Ttl(MtaDeliveryExpirationTtl { expire: Duration::from_millis(3 * 24 * 60 * 60 * 1000), }), notify: MtaDeliveryScheduleIntervalsOrDefault::Default, retry: MtaDeliveryScheduleIntervalsOrDefault::Default, queue_id: 0u64.into(), }, MtaDeliverySchedule { name: "remote".into(), description: "Remote delivery schedule".to_string().into(), expiry: MtaDeliveryExpiration::Ttl(MtaDeliveryExpirationTtl { expire: Duration::from_millis(3 * 24 * 60 * 60 * 1000), }), notify: MtaDeliveryScheduleIntervalsOrDefault::Default, retry: MtaDeliveryScheduleIntervalsOrDefault::Default, queue_id: 1u64.into(), }, MtaDeliverySchedule { name: "dsn".into(), description: "Delivery Status Notification delivery schedule" .to_string() .into(), expiry: MtaDeliveryExpiration::Attempts(MtaDeliveryExpirationAttempts { max_attempts: 10, }), notify: MtaDeliveryScheduleIntervalsOrDefault::Default, retry: MtaDeliveryScheduleIntervalsOrDefault::Custom( MtaDeliveryScheduleIntervals { intervals: List::from_iter([ MtaDeliveryScheduleInterval { duration: Duration::from_millis(15 * 60 * 1000), }, MtaDeliveryScheduleInterval { duration: Duration::from_millis(30 * 60 * 1000), }, MtaDeliveryScheduleInterval { duration: Duration::from_millis(60 * 60 * 1000), }, MtaDeliveryScheduleInterval { duration: Duration::from_millis(2 * 60 * 60 * 1000), }, ]), }, ), queue_id: 2u64.into(), }, MtaDeliverySchedule { name: "report".into(), description: "DMARC and TLS report delivery schedule".to_string().into(), expiry: MtaDeliveryExpiration::Attempts(MtaDeliveryExpirationAttempts { max_attempts: 8, }), notify: MtaDeliveryScheduleIntervalsOrDefault::Custom(Default::default()), retry: MtaDeliveryScheduleIntervalsOrDefault::Custom( MtaDeliveryScheduleIntervals { intervals: List::from_iter([ MtaDeliveryScheduleInterval { duration: Duration::from_millis(30 * 60 * 1000), }, MtaDeliveryScheduleInterval { duration: Duration::from_millis(60 * 60 * 1000), }, MtaDeliveryScheduleInterval { duration: Duration::from_millis(2 * 60 * 60 * 1000), }, ]), }, ), queue_id: 3u64.into(), }, ] .into_iter() .enumerate() { bp.registry .write(RegistryWrite::insert_with_id( (id as u64).into(), &object.into(), )) .await?; } } if bp.registry.count_object(ObjectType::MtaTlsStrategy).await? == 0 { for object in [ MtaTlsStrategy { name: "invalid-tls".into(), description: "Allow invalid TLS certificates".to_string().into(), allow_invalid_certs: true, ..Default::default() }, MtaTlsStrategy { name: "default".into(), description: "Default TLS settings".to_string().into(), allow_invalid_certs: false, ..Default::default() }, ] { bp.registry .write(RegistryWrite::insert(&object.into())) .await?; } } if bp.registry.count_object(ObjectType::MtaRoute).await? == 0 { for object in [ MtaRoute::Mx(MtaRouteMx { description: "MX delivery route".to_string().into(), ip_lookup_strategy: MtaIpStrategy::V4ThenV6, max_multihomed: 2, max_mx_hosts: 2, name: "default".into(), }), MtaRoute::Local(MtaRouteCommon { description: "Local delivery route".to_string().into(), name: "local".into(), }), ] { bp.registry .write(RegistryWrite::insert(&object.into())) .await?; } } if bp .registry .count_object(ObjectType::MtaConnectionStrategy) .await? == 0 { bp.registry .write(RegistryWrite::insert( &MtaConnectionStrategy { name: "default".into(), description: "Default connection strategy".to_string().into(), ..Default::default() } .into(), )) .await?; } if bp.registry.count_object(ObjectType::OidcProvider).await? == 0 { let pkcs8_doc = EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, &SystemRandom::new()) .map_err(|err| { trc::EventType::Server(trc::ServerEvent::Startup) .into_err() .reason(err) .caused_by(trc::location!()) })?; let signature_pem = pem::encode(&pem::Pem::new("PRIVATE KEY", pkcs8_doc.as_ref())); bp.registry .write(RegistryWrite::insert( &OidcProvider { encryption_key: SecretKey::Value(SecretKeyValue { secret: rng() .sample_iter(Alphanumeric) .take(64) .map(char::from) .collect::(), }), signature_key: SecretText::Text(SecretTextValue { secret: signature_pem, }), signature_algorithm: JwtSignatureAlgorithm::Es256, ..Default::default() } .into(), )) .await?; } if bp.registry.count_object(ObjectType::Role).await? == 0 { let permissions = DefaultPermissions::default(); let mut role_ids = Vec::with_capacity(4); for role in [ Role { description: "User".into(), enabled_permissions: Map::new(permissions.user), ..Default::default() }, Role { description: "Group".into(), enabled_permissions: Map::new(permissions.group), ..Default::default() }, Role { description: "Tenant Administrator".into(), enabled_permissions: Map::new(permissions.tenant), ..Default::default() }, Role { description: "System Administrator".into(), enabled_permissions: Map::new(permissions.superuser), ..Default::default() }, ] { match bp .registry .write(RegistryWrite::insert(&role.into())) .await? { RegistryWriteResult::Success(id) => role_ids.push(id), err => { bp.build_error( ObjectType::Role.singleton(), format!("Failed to insert default role: {err}"), ); } } } if bp.registry.count_object(ObjectType::Authentication).await? == 0 && role_ids.len() == 4 { bp.registry .write(RegistryWrite::insert( &Authentication { default_user_role_ids: Map::new(vec![role_ids[0]]), default_group_role_ids: Map::new(vec![role_ids[1]]), default_tenant_role_ids: Map::new(vec![role_ids[2], role_ids[0]]), default_admin_role_ids: Map::new(vec![role_ids[3], role_ids[0]]), ..Default::default() } .into(), )) .await?; } } if bp .registry .count_object(ObjectType::NetworkListener) .await? == 0 { for (protocol, name, port, tls_implicit) in [ (NetworkListenerProtocol::Smtp, "smtp", 25, false), (NetworkListenerProtocol::Smtp, "submissions", 465, true), (NetworkListenerProtocol::Imap, "imaps", 993, true), (NetworkListenerProtocol::Pop3, "pop3s", 995, true), (NetworkListenerProtocol::ManageSieve, "sieve", 4190, false), (NetworkListenerProtocol::Http, "https", 443, true), (NetworkListenerProtocol::Http, "http", 8080, false), ] { bp.registry .write(RegistryWrite::insert( &NetworkListener { bind: Map::new(vec![ SocketAddr::from_str(&format!("[::]:{port}")).unwrap(), ]), name: name.to_string(), protocol, use_tls: true, tls_implicit, ..Default::default() } .into(), )) .await?; } } #[cfg(not(any(feature = "dev_mode", feature = "test_mode")))] if bp.registry.count_object(ObjectType::Asn).await? == 0 { bp.registry .write(RegistryWrite::insert( &Asn::Resource(AsnResource { asn_urls: Map::new(vec![ASN_IPV4.into(), ASN_IPV6.into()]), geo_urls: Map::new(vec![GEO_IPV4.into(), GEO_IPV6.into()]), max_size: 104857600, expires: Duration::from_millis(24 * 60 * 60 * 1000), timeout: Duration::from_millis(5 * 60 * 1000), ..Default::default() }) .into(), )) .await?; } #[cfg(not(feature = "test_mode"))] if bp.registry.count_object(ObjectType::TracingStore).await? == 0 { bp.registry .write(RegistryWrite::insert(&TracingStore::Default.into())) .await?; } #[cfg(not(feature = "test_mode"))] if bp.registry.count_object(ObjectType::MetricsStore).await? == 0 { bp.registry .write(RegistryWrite::insert(&MetricsStore::Default.into())) .await?; } if bp.registry.count_object(ObjectType::Tracer).await? == 0 { bp.registry .write(RegistryWrite::insert( &Tracer::Log(TracerLog { enable: true, ansi: false, prefix: "stalwart.log".into(), rotate: LogRotateFrequency::Daily, path: "/var/log/stalwart".into(), ..Default::default() }) .into(), )) .await?; } #[cfg(not(feature = "test_mode"))] { use store::write::BatchBuilder; use types::id::Id; if bp.registry.count_object(ObjectType::SpamRule).await? == 0 && bp .registry .object::(Id::singleton()) .await? .is_none_or(|spam| spam.spam_filter_rules_url.is_some()) { let mut batch = BatchBuilder::new(); batch.schedule_task(Task::SpamFilterMaintenance(TaskSpamFilterMaintenance { maintenance_type: TaskSpamFilterMaintenanceType::UpdateRules, status: TaskStatus::now(), })); bp.data_store.write(batch.build_all()).await?; } } Ok(()) }