Exclude SEL licensed code from AGPL licensed code (closes #783 closes #768)

This commit is contained in:
mdecimus 2025-07-21 12:33:20 +02:00
parent dd0226b75a
commit 9310083222
43 changed files with 655 additions and 71 deletions

View file

@ -62,12 +62,12 @@ impl Server {
// Apply principal permissions
let mut permissions = role_permissions.finalize();
let mut tenant = None;
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
let mut tenant = None;
#[cfg(feature = "enterprise")]
if self.is_enterprise_edition() {
if let Some(tenant_id) = principal.tenant {

View file

@ -189,8 +189,12 @@ impl Core {
}
Self {
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
enterprise,
// SPDX-SnippetEnd
sieve: Scripting::parse(config, &stores).await,
network: Network::parse(config),
smtp: SmtpConfig::parse(config).await,

View file

@ -40,8 +40,12 @@ pub enum TelemetrySubscriberType {
Webhook(WebhookTracer),
#[cfg(unix)]
JournalTracer(crate::telemetry::tracers::journald::Subscriber),
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
StoreTracer(StoreTracer),
// SPDX-SnippetEnd
}
#[derive(Debug)]
@ -88,11 +92,15 @@ pub struct WebhookTracer {
pub headers: HeaderMap,
}
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[derive(Debug)]
#[cfg(feature = "enterprise")]
pub struct StoreTracer {
pub store: store::Store,
}
// SPDX-SnippetEnd
#[derive(Debug)]
pub enum RotationStrategy {
@ -477,8 +485,12 @@ impl Tracers {
TelemetrySubscriberType::JournalTracer(_) => {
EventType::Telemetry(TelemetryEvent::JournalError).into()
}
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
TelemetrySubscriberType::StoreTracer(_) => None,
// SPDX-SnippetEnd
};
// Parse disabled events
@ -509,6 +521,10 @@ impl Tracers {
}
}
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
// Parse tracing history
#[cfg(feature = "enterprise")]
{
@ -540,6 +556,7 @@ impl Tracers {
}
}
}
// SPDX-SnippetEnd
// Parse webhooks
for id in config.sub_keys("webhook", ".url") {

View file

@ -51,8 +51,6 @@ pub mod auth;
pub mod config;
pub mod core;
pub mod dns;
#[cfg(feature = "enterprise")]
pub mod enterprise;
pub mod expr;
pub mod i18n;
pub mod ipc;
@ -63,6 +61,15 @@ pub mod sharing;
pub mod storage;
pub mod telemetry;
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
pub mod enterprise;
// SPDX-SnippetEnd
pub use psl;
pub static VERSION_PRIVATE: &str = env!("CARGO_PKG_VERSION");
@ -338,8 +345,13 @@ pub struct Core {
pub spam: SpamFilterConfig,
pub imap: ImapConfig,
pub metrics: Metrics,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
pub enterprise: Option<enterprise::Enterprise>,
// SPDX-SnippetEnd
}
impl<T: CacheItemWeight> CacheItemWeight for CacheSwap<T> {

View file

@ -427,8 +427,14 @@ impl BootManager {
let cache = Caches::parse(&mut config);
// Enable telemetry
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
telemetry.enable(core.is_enterprise_edition());
// SPDX-SnippetEnd
#[cfg(not(feature = "enterprise"))]
telemetry.enable(false);

View file

@ -4,14 +4,11 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::time::Duration;
use hyper::HeaderMap;
use utils::HttpLimitResponse;
use crate::USER_AGENT;
use self::config::ConfigManager;
use crate::USER_AGENT;
use hyper::HeaderMap;
use std::time::Duration;
use utils::HttpLimitResponse;
pub mod backup;
pub mod boot;
@ -23,9 +20,19 @@ pub mod webadmin;
const DEFAULT_SPAMFILTER_URL: &str =
"https://github.com/stalwartlabs/spam-filter/releases/latest/download/spam-filter.toml";
pub const WEBADMIN_KEY: &[u8] = "STALWART_WEBADMIN".as_bytes();
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
const DEFAULT_WEBADMIN_URL: &str =
"https://github.com/stalwartlabs/webadmin/releases/latest/download/webadmin.zip";
pub const WEBADMIN_KEY: &[u8] = "STALWART_WEBADMIN".as_bytes();
// SPDX-SnippetEnd
#[cfg(not(feature = "enterprise"))]
const DEFAULT_WEBADMIN_URL: &str =
"https://github.com/stalwartlabs/webadmin/releases/latest/download/webadmin-oss.zip";
impl ConfigManager {
pub async fn fetch_resource(&self, resource_id: &str) -> Result<Vec<u8>, String> {

View file

@ -7,5 +7,9 @@
pub mod otel;
pub mod prometheus;
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
pub mod store;
// SPDX-SnippetEnd

View file

@ -16,8 +16,12 @@ impl Server {
pub async fn export_prometheus_metrics(&self) -> trc::Result<String> {
let mut metrics = Vec::new();
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
let is_enterprise = self.is_enterprise_edition();
// SPDX-SnippetEnd
#[cfg(not(feature = "enterprise"))]
let is_enterprise = false;

View file

@ -108,12 +108,15 @@ impl TelemetrySubscriberType {
TelemetrySubscriberType::JournalTracer(subscriber) => {
tracers::journald::spawn_journald_tracer(builder, subscriber)
}
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
TelemetrySubscriberType::StoreTracer(subscriber) => {
if is_enterprise {
tracers::store::spawn_store_tracer(builder, subscriber)
}
}
} // SPDX-SnippetEnd
}
}
}

View file

@ -10,5 +10,9 @@ pub mod log;
pub mod otel;
pub mod stdout;
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
pub mod store;
// SPDX-SnippetEnd

View file

@ -646,12 +646,13 @@ impl ManageDirectory for Store {
let mut batch = BatchBuilder::new();
batch.with_account_id(u32::MAX);
let tenant = principal.tenant.as_ref().map(|t| t.to_native());
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
// Make sure tenant has no data
let tenant = principal.tenant.as_ref().map(|t| t.to_native());
#[cfg(feature = "enterprise")]
match typ {
Type::Individual | Type::Group => {

View file

@ -20,24 +20,20 @@ pub struct PrincipalInfo {
pub tenant: Option<u32>,
}
#[cfg(feature = "enterprise")]
impl PrincipalInfo {
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
pub fn has_tenant_access(&self, tenant_id: Option<u32>) -> bool {
tenant_id.is_none_or(|tenant_id| {
self.tenant.is_some_and(|t| tenant_id == t)
|| (self.typ == Type::Tenant && self.id == tenant_id)
})
}
// SPDX-SnippetEnd
}
#[cfg(not(feature = "enterprise"))]
impl PrincipalInfo {
#[cfg(not(feature = "enterprise"))]
pub fn has_tenant_access(&self, _tenant_id: Option<u32>) -> bool {
true
}

View file

@ -91,6 +91,9 @@ impl Directories {
// Build directory
if let Some(store) = store {
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
if store.is_enterprise_directory() && !is_enterprise {
let message =
@ -98,6 +101,7 @@ impl Directories {
config.new_parse_error(("directory", id, "type"), message);
continue;
}
// SPDX-SnippetEnd
let directory = Arc::new(Directory {
store,

View file

@ -75,11 +75,18 @@ impl Principal {
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
pub fn tenant(&self) -> Option<u32> {
self.tenant
}
// SPDX-SnippetEnd
#[cfg(not(feature = "enterprise"))]
pub fn tenant(&self) -> Option<u32> {
None
}
pub fn description(&self) -> Option<&str> {
self.description.as_deref()
}

View file

@ -507,6 +507,9 @@ enum Response {
}
fn render_response(server: &Server, response: Response, language: &str) -> String {
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
let template = server
.core
@ -514,6 +517,8 @@ fn render_response(server: &Server, response: Response, language: &str) -> Strin
.as_ref()
.and_then(|e| e.template_scheduling_web.as_ref())
.unwrap_or(&server.core.groupware.itip_template);
// SPDX-SnippetEnd
#[cfg(not(feature = "enterprise"))]
let template = &server.core.groupware.itip_template;
let locale = i18n::locale_or_default(language);

View file

@ -130,8 +130,13 @@ impl OAuthApiHandler for Server {
#[cfg(not(feature = "enterprise"))]
let is_enterprise = false;
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
let is_enterprise = self.core.is_enterprise_edition();
// SPDX-SnippetEnd
json!({
"data": {

View file

@ -7,8 +7,6 @@
pub mod crypto;
pub mod dkim;
pub mod dns;
#[cfg(feature = "enterprise")]
pub mod enterprise;
pub mod log;
pub mod principal;
pub mod queue;
@ -19,15 +17,23 @@ pub mod spam;
pub mod stores;
pub mod troubleshoot;
use std::{str::FromStr, sync::Arc};
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
pub mod enterprise;
#[cfg(feature = "enterprise")]
use enterprise::telemetry::TelemetryApi;
// SPDX-SnippetEnd
use crate::auth::oauth::auth::OAuthApiHandler;
use common::{Server, auth::AccessToken};
use crypto::CryptoHandler;
use directory::{Permission, backend::internal::manage};
use dkim::DkimManagement;
use dns::DnsManagement;
#[cfg(feature = "enterprise")]
use enterprise::telemetry::TelemetryApi;
use http_proto::{request::fetch_body, *};
use hyper::{Method, StatusCode, header};
use jmap::api::{ToJmapHttpResponse, ToRequestError};
use jmap_proto::error::request::RequestError;
@ -40,15 +46,12 @@ use report::ManageReports;
use serde::Serialize;
use settings::ManageSettings;
use spam::ManageSpamHandler;
use std::future::Future;
use std::{str::FromStr, sync::Arc};
use store::write::now;
use stores::ManageStore;
use troubleshoot::TroubleshootApi;
use crate::auth::oauth::auth::OAuthApiHandler;
use http_proto::{request::fetch_body, *};
use std::future::Future;
#[derive(Serialize)]
#[serde(tag = "error")]
#[serde(rename_all = "camelCase")]

View file

@ -252,12 +252,11 @@ impl PrincipalManager for Server {
})?;
}
let mut tenant = access_token.tenant.map(|t| t.id);
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
let mut tenant = access_token.tenant.map(|t| t.id);
#[cfg(feature = "enterprise")]
if self.core.is_enterprise_edition() {
if tenant.is_none() {
@ -276,7 +275,6 @@ impl PrincipalManager for Server {
} else if types.contains(&Type::Tenant) {
return Err(manage::enterprise());
}
// SPDX-SnippetEnd
let principals = self
@ -347,6 +345,9 @@ impl PrincipalManager for Server {
let mut tenant = access_token.tenant.map(|t| t.id);
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
if self.core.is_enterprise_edition() {
if tenant.is_none() {
@ -365,6 +366,7 @@ impl PrincipalManager for Server {
} else if typ == Type::Tenant {
return Err(manage::enterprise());
}
// SPDX-SnippetEnd
let principals = self
.store()

View file

@ -137,13 +137,13 @@ impl QueueManagement for Server {
access_token: &AccessToken,
) -> trc::Result<HttpResponse> {
let params = UrlParams::new(req.uri().query());
let mut tenant_domains: Option<Vec<String>> = None;
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
// Limit to tenant domains
let mut tenant_domains: Option<Vec<String>> = None;
#[cfg(feature = "enterprise")]
if self.core.is_enterprise_edition() {
if let Some(tenant) = access_token.tenant {

View file

@ -83,8 +83,13 @@ impl ManageReload for Server {
if let Some(tracers) = result.tracers {
// Update tracers
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
tracers.update(self.inner.shared_core.load().is_enterprise_edition());
// SPDX-SnippetEnd
#[cfg(not(feature = "enterprise"))]
tracers.update(false);
}

View file

@ -46,12 +46,12 @@ impl ManageReports for Server {
path: Vec<&str>,
access_token: &AccessToken,
) -> trc::Result<HttpResponse> {
let mut tenant_domains: Option<Vec<String>> = None;
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
// Limit to tenant domains
let mut tenant_domains: Option<Vec<String>> = None;
#[cfg(feature = "enterprise")]
if self.core.is_enterprise_edition() {
if let Some(tenant) = access_token.tenant {

View file

@ -17,10 +17,12 @@ use directory::{
backend::internal::manage::{self, ManageDirectory},
};
use email::message::{ingest::EmailIngest, metadata::MessageData};
use http_proto::{request::decode_path_element, *};
use hyper::Method;
use jmap_proto::types::{collection::Collection, property::Property};
use serde_json::json;
use services::task_manager::fts::FtsIndexTask;
use std::future::Future;
use store::{
Serialize, rand,
write::{Archiver, BatchBuilder, ValueClass},
@ -28,11 +30,12 @@ use store::{
use trc::AddContext;
use utils::url_params::UrlParams;
use http_proto::{request::decode_path_element, *};
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
use super::enterprise::undelete::UndeleteApi;
use std::future::Future;
// SPDX-SnippetEnd
pub trait ManageStore: Sync + Send {
fn handle_manage_store(

View file

@ -581,12 +581,11 @@ impl ParseHttp for Server {
}
_ => (),
},
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
"logo.svg" if self.is_enterprise_edition() => {
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
match self
.logo_resource(
req.headers()
@ -611,9 +610,8 @@ impl ParseHttp for Server {
if !resource.is_empty() {
return Ok(resource.into_http_response());
}
// SPDX-SnippetEnd
}
// SPDX-SnippetEnd
"form" => {
if let Some(form) = &self.core.network.contact_form {
match *req.method() {

View file

@ -65,4 +65,5 @@ enterprise = [ "jmap/enterprise",
"http/enterprise",
"dav/enterprise",
"groupware/enterprise",
"trc/enterprise",
"services/enterprise" ]

View file

@ -50,9 +50,13 @@ async fn main() -> std::io::Result<()> {
init.config.log_errors();
init.config.log_warnings();
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
// Log licensing information
#[cfg(feature = "enterprise")]
init.inner.build_server().log_license_details();
// SPDX-SnippetEnd
// Spawn servers
let (shutdown_tx, shutdown_rx) = init.servers.spawn(|server, acceptor, shutdown_rx| {

View file

@ -4,31 +4,33 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::{
collections::BinaryHeap,
future::Future,
sync::Arc,
time::{Duration, Instant, SystemTime},
};
use common::{
Inner, KV_LOCK_HOUSEKEEPER, LONG_1D_SLUMBER, Server,
config::telemetry::OtelMetrics,
core::BuildServer,
ipc::{BroadcastEvent, HousekeeperEvent, PurgeType},
};
use email::message::delete::EmailDeletion;
use smtp::reporting::SmtpReporting;
use std::{
collections::BinaryHeap,
future::Future,
sync::Arc,
time::{Duration, Instant, SystemTime},
};
use store::{PurgeStore, write::now};
use tokio::sync::mpsc;
use trc::{Collector, MetricType, PurgeEvent};
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
use common::telemetry::{
metrics::store::{MetricsStore, SharedMetricHistory},
tracers::store::TracingStore,
};
use email::message::delete::EmailDeletion;
use smtp::reporting::SmtpReporting;
use store::{PurgeStore, write::now};
use tokio::sync::mpsc;
use trc::{Collector, MetricType, PurgeEvent};
// SPDX-SnippetEnd
#[derive(PartialEq, Eq)]
struct Action {
@ -42,13 +44,17 @@ enum ActionClass {
Store(usize),
Acme(String),
OtelMetrics,
CalculateMetrics,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
InternalMetrics,
CalculateMetrics,
#[cfg(feature = "enterprise")]
AlertMetrics,
#[cfg(feature = "enterprise")]
RenewLicense,
// SPDX-SnippetEnd
}
#[derive(Default)]
@ -56,8 +62,12 @@ struct Queue {
heap: BinaryHeap<Action>,
}
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
const METRIC_ALERTS_INTERVAL: Duration = Duration::from_secs(5 * 60);
// SPDX-SnippetEnd
pub fn spawn_housekeeper(inner: Arc<Inner>, mut rx: mpsc::Receiver<HousekeeperEvent>) {
tokio::spawn(async move {
@ -143,12 +153,18 @@ pub fn spawn_housekeeper(inner: Arc<Inner>, mut rx: mpsc::Receiver<HousekeeperEv
);
}
}
// SPDX-SnippetEnd
}
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
// Metrics history
#[cfg(feature = "enterprise")]
let metrics_history = SharedMetricHistory::default();
// SPDX-SnippetEnd
let mut next_metric_update = Instant::now();
loop {
@ -383,8 +399,12 @@ pub fn spawn_housekeeper(inner: Arc<Inner>, mut rx: mpsc::Receiver<HousekeeperEv
let otel = otel.clone();
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
let is_enterprise = server.is_enterprise_edition();
// SPDX-SnippetEnd
#[cfg(not(feature = "enterprise"))]
let is_enterprise = false;
@ -417,6 +437,9 @@ pub fn spawn_housekeeper(inner: Arc<Inner>, mut rx: mpsc::Receiver<HousekeeperEv
let server = server.clone();
tokio::spawn(async move {
if server.core.network.roles.calculate_metrics {
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
if server.is_enterprise_edition() {
// Obtain queue size
@ -434,6 +457,7 @@ pub fn spawn_housekeeper(inner: Arc<Inner>, mut rx: mpsc::Receiver<HousekeeperEv
}
}
}
// SPDX-SnippetEnd
if update_other_metrics {
match server.total_accounts().await {

View file

@ -431,6 +431,9 @@ async fn build_template(
access_token.emails.first().unwrap().to_string()
};
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
let template = server
.core
@ -438,6 +441,8 @@ async fn build_template(
.as_ref()
.and_then(|e| e.template_calendar_alarm.as_ref())
.unwrap_or(&server.core.groupware.alarms_template);
// SPDX-SnippetEnd
#[cfg(not(feature = "enterprise"))]
let template = &server.core.groupware.alarms_template;
let locale = i18n::locale_or_default(access_token.locale.as_deref().unwrap_or("en"));

View file

@ -310,6 +310,9 @@ pub async fn build_itip_template(
summary: &ArchivedItipSummary,
logo_cid: &str,
) -> Details {
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
let template = server
.core
@ -317,6 +320,7 @@ pub async fn build_itip_template(
.as_ref()
.and_then(|e| e.template_scheduling_email.as_ref())
.unwrap_or(&server.core.groupware.itip_template);
// SPDX-SnippetEnd
#[cfg(not(feature = "enterprise"))]
let template = &server.core.groupware.itip_template;
let locale = i18n::locale_or_default(access_token.locale.as_deref().unwrap_or("en"));

View file

@ -27,8 +27,6 @@ pub mod headers;
pub mod html;
pub mod init;
pub mod ip;
#[cfg(feature = "enterprise")]
pub mod llm;
pub mod messageid;
pub mod mime;
pub mod pyzor;
@ -42,6 +40,13 @@ pub mod subject;
pub mod trusted_reply;
pub mod url;
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
pub mod llm;
// SPDX-SnippetEnd
impl SpamFilterInput<'_> {
pub fn header_as_address(&self, header: &Header<'_>) -> Option<Cow<'_, str>> {
self.message

View file

@ -4,9 +4,6 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use common::{Server, config::spamfilter::SpamFilterAction};
use std::{fmt::Write, future::Future, vec};
use crate::{
SpamFilterContext,
analysis::{
@ -22,9 +19,15 @@ use crate::{
},
modules::bayes::BayesClassifier,
};
use common::{Server, config::spamfilter::SpamFilterAction};
use std::{fmt::Write, future::Future, vec};
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
use crate::analysis::llm::SpamFilterAnalyzeLlm;
// SPDX-SnippetEnd
pub trait SpamFilterAnalyzeScore: Sync + Send {
fn spam_filter_score(
@ -190,10 +193,16 @@ impl SpamFilterAnalyzeScore for Server {
// HTML content analysis
self.spam_filter_analyze_html(ctx).await;
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
// LLM classification
#[cfg(feature = "enterprise")]
self.spam_filter_analyze_llm(ctx).await;
// SPDX-SnippetEnd
// Trusted reply analysis
self.spam_filter_analyze_reply_in(ctx).await;

View file

@ -71,11 +71,15 @@ impl ShardedBlob {
Store::MySQL(store) => store.get_blob(key, read_range).await,
#[cfg(feature = "rocks")]
Store::RocksDb(store) => store.get_blob(key, read_range).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(
feature = "enterprise",
any(feature = "postgres", feature = "mysql")
))]
Store::SQLReadReplica(store) => store.get_blob(key, read_range).await,
// SPDX-SnippetEnd
Store::None => Err(trc::StoreEvent::NotConfigured.into()),
},
BlobBackend::Fs(store) => store.get_blob(key, read_range).await,
@ -103,10 +107,14 @@ impl ShardedBlob {
Store::MySQL(store) => store.put_blob(key, data).await,
#[cfg(feature = "rocks")]
Store::RocksDb(store) => store.put_blob(key, data).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(
feature = "enterprise",
any(feature = "postgres", feature = "mysql")
))]
// SPDX-SnippetEnd
Store::SQLReadReplica(store) => store.put_blob(key, data).await,
Store::None => Err(trc::StoreEvent::NotConfigured.into()),
},
@ -135,11 +143,15 @@ impl ShardedBlob {
Store::MySQL(store) => store.delete_blob(key).await,
#[cfg(feature = "rocks")]
Store::RocksDb(store) => store.delete_blob(key).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(
feature = "enterprise",
any(feature = "postgres", feature = "mysql")
))]
Store::SQLReadReplica(store) => store.delete_blob(key).await,
// SPDX-SnippetEnd
Store::None => Err(trc::StoreEvent::NotConfigured.into()),
},
BlobBackend::Fs(store) => store.delete_blob(key).await,

View file

@ -6,8 +6,6 @@
#[cfg(feature = "azure")]
pub mod azure;
#[cfg(feature = "enterprise")]
pub mod composite;
#[cfg(feature = "elastic")]
pub mod elastic;
#[cfg(feature = "foundation")]
@ -34,6 +32,13 @@ pub mod sqlite;
#[cfg(feature = "zenoh")]
pub mod zenoh;
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
pub mod composite;
// SPDX-SnippetEnd
pub const MAX_TOKEN_LENGTH: usize = (u8::MAX >> 1) as usize;
pub const MAX_TOKEN_MASK: usize = MAX_TOKEN_LENGTH - 1;

View file

@ -10,6 +10,9 @@ use crate::{
};
use utils::config::{Config, cron::SimpleCron, utils::ParseValue};
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
enum CompositeStore {
#[cfg(any(feature = "postgres", feature = "mysql"))]
@ -17,6 +20,7 @@ enum CompositeStore {
ShardedBlob(String),
ShardedInMemory(String),
}
// SPDX-SnippetEnd
impl Stores {
pub async fn parse_all(config: &mut Config, is_reload: bool) -> Self {
@ -33,8 +37,13 @@ impl Stores {
pub async fn parse_stores(&mut self, config: &mut Config) {
let is_reload = !self.stores.is_empty();
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
let mut composite_stores = Vec::new();
// SPDX-SnippetEnd
for store_id in config.sub_keys("store", ".type") {
let id = store_id.as_str();
@ -239,6 +248,9 @@ impl Stores {
.insert(store_id, crate::PubSubStore::Kafka(db));
}
}
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
"sql-read-replica" => {
#[cfg(any(feature = "postgres", feature = "mysql"))]
@ -252,6 +264,7 @@ impl Stores {
"sharded-in-memory" => {
composite_stores.push(CompositeStore::ShardedInMemory(store_id));
}
// SPDX-SnippetEnd
#[cfg(feature = "azure")]
"azure" => {
if let Some(db) = crate::backend::azure::AzureStore::open(config, prefix)
@ -271,6 +284,9 @@ impl Stores {
}
}
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
for composite_store in composite_stores {
match composite_store {
@ -332,6 +348,8 @@ impl Stores {
}
}
}
// SPDX-SnippetEnd
}
pub async fn parse_in_memory(&mut self, config: &mut Config, is_reload: bool) {

View file

@ -30,8 +30,12 @@ impl BlobStore {
Store::MySQL(store) => store.get_blob(key, read_range).await,
#[cfg(feature = "rocks")]
Store::RocksDb(store) => store.get_blob(key, read_range).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
Store::SQLReadReplica(store) => store.get_blob(key, read_range).await,
// SPDX-SnippetEnd
Store::None => Err(trc::StoreEvent::NotConfigured.into()),
},
BlobBackend::Fs(store) => store.get_blob(key, read_range).await,
@ -39,8 +43,12 @@ impl BlobStore {
BlobBackend::S3(store) => store.get_blob(key, read_range).await,
#[cfg(feature = "azure")]
BlobBackend::Azure(store) => store.get_blob(key, read_range).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
BlobBackend::Sharded(store) => store.get_blob(key, read_range).await,
// SPDX-SnippetEnd
};
trc::event!(
@ -112,8 +120,12 @@ impl BlobStore {
Store::MySQL(store) => store.put_blob(key, data.as_ref()).await,
#[cfg(feature = "rocks")]
Store::RocksDb(store) => store.put_blob(key, data.as_ref()).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
Store::SQLReadReplica(store) => store.put_blob(key, data.as_ref()).await,
// SPDX-SnippetEnd
Store::None => Err(trc::StoreEvent::NotConfigured.into()),
},
BlobBackend::Fs(store) => store.put_blob(key, data.as_ref()).await,
@ -121,8 +133,12 @@ impl BlobStore {
BlobBackend::S3(store) => store.put_blob(key, data.as_ref()).await,
#[cfg(feature = "azure")]
BlobBackend::Azure(store) => store.put_blob(key, data.as_ref()).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
BlobBackend::Sharded(store) => store.put_blob(key, data.as_ref()).await,
// SPDX-SnippetEnd
}
.caused_by(trc::location!());
@ -150,8 +166,12 @@ impl BlobStore {
Store::MySQL(store) => store.delete_blob(key).await,
#[cfg(feature = "rocks")]
Store::RocksDb(store) => store.delete_blob(key).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
Store::SQLReadReplica(store) => store.delete_blob(key).await,
// SPDX-SnippetEnd
Store::None => Err(trc::StoreEvent::NotConfigured.into()),
},
BlobBackend::Fs(store) => store.delete_blob(key).await,
@ -159,8 +179,12 @@ impl BlobStore {
BlobBackend::S3(store) => store.delete_blob(key).await,
#[cfg(feature = "azure")]
BlobBackend::Azure(store) => store.delete_blob(key).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
BlobBackend::Sharded(store) => store.delete_blob(key).await,
// SPDX-SnippetEnd
}
.caused_by(trc::location!());

View file

@ -49,8 +49,12 @@ impl InMemoryStore {
}
#[cfg(feature = "redis")]
InMemoryStore::Redis(store) => store.key_set(&kv.key, &kv.value, kv.expires).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
InMemoryStore::Sharded(store) => store.key_set(kv).await,
// SPDX-SnippetEnd
InMemoryStore::Static(_) | InMemoryStore::Http(_) => {
Err(trc::StoreEvent::NotSupported.into_err())
}
@ -97,8 +101,12 @@ impl InMemoryStore {
}
#[cfg(feature = "redis")]
InMemoryStore::Redis(store) => store.key_incr(&kv.key, kv.value, kv.expires).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
InMemoryStore::Sharded(store) => store.counter_incr(kv).await,
// SPDX-SnippetEnd
InMemoryStore::Static(_) | InMemoryStore::Http(_) => {
Err(trc::StoreEvent::NotSupported.into_err())
}
@ -118,8 +126,12 @@ impl InMemoryStore {
}
#[cfg(feature = "redis")]
InMemoryStore::Redis(store) => store.key_delete(key.into().as_bytes()).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
InMemoryStore::Sharded(store) => store.key_delete(key).await,
// SPDX-SnippetEnd
InMemoryStore::Static(_) | InMemoryStore::Http(_) => {
Err(trc::StoreEvent::NotSupported.into_err())
}
@ -139,8 +151,12 @@ impl InMemoryStore {
}
#[cfg(feature = "redis")]
InMemoryStore::Redis(store) => store.key_delete(key.into().as_bytes()).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
InMemoryStore::Sharded(store) => store.counter_delete(key).await,
// SPDX-SnippetEnd
InMemoryStore::Static(_) | InMemoryStore::Http(_) => {
Err(trc::StoreEvent::NotSupported.into_err())
}
@ -180,8 +196,12 @@ impl InMemoryStore {
}
#[cfg(feature = "redis")]
InMemoryStore::Redis(store) => store.key_delete_prefix(prefix).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
InMemoryStore::Sharded(store) => store.key_delete_prefix(prefix).await,
// SPDX-SnippetEnd
InMemoryStore::Static(_) | InMemoryStore::Http(_) => {
Err(trc::StoreEvent::NotSupported.into_err())
}
@ -202,8 +222,12 @@ impl InMemoryStore {
.map(|value| value.and_then(|v| v.into())),
#[cfg(feature = "redis")]
InMemoryStore::Redis(store) => store.key_get(key.into().as_bytes()).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
InMemoryStore::Sharded(store) => store.key_get(key).await,
// SPDX-SnippetEnd
InMemoryStore::Static(store) => Ok(store
.get(key.into().as_str())
.map(|value| T::from(value.clone()))),
@ -225,8 +249,12 @@ impl InMemoryStore {
}
#[cfg(feature = "redis")]
InMemoryStore::Redis(store) => store.counter_get(key.into().as_bytes()).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
InMemoryStore::Sharded(store) => store.counter_get(key).await,
// SPDX-SnippetEnd
InMemoryStore::Static(_) | InMemoryStore::Http(_) => {
Err(trc::StoreEvent::NotSupported.into_err())
}
@ -244,8 +272,12 @@ impl InMemoryStore {
.map(|value| matches!(value, Some(LookupValue::Value(())))),
#[cfg(feature = "redis")]
InMemoryStore::Redis(store) => store.key_exists(key.into().as_bytes()).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
InMemoryStore::Sharded(store) => store.key_exists(key).await,
// SPDX-SnippetEnd
InMemoryStore::Static(store) => Ok(store.get(key.into().as_str()).is_some()),
InMemoryStore::Http(store) => Ok(store.contains(key.into().as_str())),
}
@ -345,11 +377,15 @@ impl InMemoryStore {
.key_incr(&KeyValue::<()>::build_key(prefix, key), 1, duration.into())
.await
.map(|count| count == 1),
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
InMemoryStore::Sharded(store) => store
.counter_incr(KeyValue::with_prefix(prefix, key, 1).expires(duration))
.await
.map(|count| count == 1),
// SPDX-SnippetEnd
InMemoryStore::Static(_) | InMemoryStore::Http(_) => {
Err(trc::StoreEvent::NotSupported.into_err())
}
@ -443,8 +479,12 @@ impl InMemoryStore {
}
#[cfg(feature = "redis")]
InMemoryStore::Redis(_) => {}
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
InMemoryStore::Sharded(_) => {}
// SPDX-SnippetEnd
InMemoryStore::Static(_) | InMemoryStore::Http(_) => {}
}

View file

@ -27,8 +27,12 @@ impl Store {
Self::MySQL(_) => "mysql",
#[cfg(feature = "rocks")]
Self::RocksDb(_) => "rocksdb",
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
Self::SQLReadReplica(_) => "read_replica",
// SPDX-SnippetEnd
Self::None => "none",
}
}

View file

@ -53,8 +53,12 @@ impl Store {
Self::MySQL(store) => store.get_value(key).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.get_value(key).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
Self::SQLReadReplica(store) => store.get_value(key).await,
// SPDX-SnippetEnd
Self::None => Err(trc::StoreEvent::NotConfigured.into()),
}
.caused_by(trc::location!())
@ -75,8 +79,12 @@ impl Store {
Self::MySQL(store) => store.get_bitmap(key).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.get_bitmap(key).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
Self::SQLReadReplica(store) => store.get_bitmap(key).await,
// SPDX-SnippetEnd
Self::None => Err(trc::StoreEvent::NotConfigured.into()),
}
.caused_by(trc::location!())
@ -121,8 +129,12 @@ impl Store {
Self::MySQL(store) => store.iterate(params, cb).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.iterate(params, cb).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
Self::SQLReadReplica(store) => store.iterate(params, cb).await,
// SPDX-SnippetEnd
Self::None => Err(trc::StoreEvent::NotConfigured.into()),
}
.caused_by(trc::location!());
@ -150,8 +162,12 @@ impl Store {
Self::MySQL(store) => store.get_counter(key).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.get_counter(key).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
Self::SQLReadReplica(store) => store.get_counter(key).await,
// SPDX-SnippetEnd
Self::None => Err(trc::StoreEvent::NotConfigured.into()),
}
.caused_by(trc::location!())
@ -199,8 +215,12 @@ impl Store {
Self::MySQL(store) => store.write(batch).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.write(batch).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
Self::SQLReadReplica(store) => store.write(batch).await,
// SPDX-SnippetEnd
Self::None => Err(trc::StoreEvent::NotConfigured.into()),
};
@ -275,8 +295,12 @@ impl Store {
Self::MySQL(store) => store.purge_store().await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.purge_store().await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
Self::SQLReadReplica(store) => store.purge_store().await,
// SPDX-SnippetEnd
Self::None => Err(trc::StoreEvent::NotConfigured.into()),
}
.caused_by(trc::location!())
@ -294,8 +318,12 @@ impl Store {
Self::MySQL(store) => store.delete_range(from, to).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.delete_range(from, to).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
Self::SQLReadReplica(store) => store.delete_range(from, to).await,
// SPDX-SnippetEnd
Self::None => Err(trc::StoreEvent::NotConfigured.into()),
}
.caused_by(trc::location!())
@ -450,8 +478,12 @@ impl Store {
Self::MySQL(store) => store.get_blob(key, range).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.get_blob(key, range).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
Self::SQLReadReplica(store) => store.get_blob(key, range).await,
// SPDX-SnippetEnd
Self::None => Err(trc::StoreEvent::NotConfigured.into()),
}
.caused_by(trc::location!())
@ -469,8 +501,12 @@ impl Store {
Self::MySQL(store) => store.put_blob(key, data).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.put_blob(key, data).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
Self::SQLReadReplica(store) => store.put_blob(key, data).await,
// SPDX-SnippetEnd
Self::None => Err(trc::StoreEvent::NotConfigured.into()),
}
.caused_by(trc::location!())
@ -488,8 +524,12 @@ impl Store {
Self::MySQL(store) => store.delete_blob(key).await,
#[cfg(feature = "rocks")]
Self::RocksDb(store) => store.delete_blob(key).await,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
Self::SQLReadReplica(store) => store.delete_blob(key).await,
// SPDX-SnippetEnd
Self::None => Err(trc::StoreEvent::NotConfigured.into()),
}
.caused_by(trc::location!())

View file

@ -173,8 +173,12 @@ pub enum Store {
MySQL(Arc<backend::mysql::MysqlStore>),
#[cfg(feature = "rocks")]
RocksDb(Arc<backend::rocksdb::RocksDbStore>),
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
SQLReadReplica(Arc<backend::composite::read_replica::SQLReadReplica>),
// SPDX-SnippetEnd
#[default]
None,
}
@ -199,8 +203,12 @@ pub enum BlobBackend {
S3(Arc<backend::s3::S3Store>),
#[cfg(feature = "azure")]
Azure(Arc<backend::azure::AzureStore>),
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
Sharded(Arc<backend::composite::sharded_blob::ShardedBlob>),
// SPDX-SnippetEnd
}
#[derive(Clone)]
@ -217,8 +225,12 @@ pub enum InMemoryStore {
Redis(Arc<backend::redis::RedisStore>),
Http(Arc<HttpStore>),
Static(Arc<StaticMemoryStore>),
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
Sharded(Arc<backend::composite::sharded_lookup::ShardedInMemory>),
// SPDX-SnippetEnd
}
#[derive(Clone, Default)]
@ -706,8 +718,12 @@ impl Store {
Store::PostgreSQL(_) => true,
#[cfg(feature = "mysql")]
Store::MySQL(_) => true,
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
Store::SQLReadReplica(_) => true,
// SPDX-SnippetEnd
_ => false,
}
}
@ -722,6 +738,9 @@ impl Store {
}
}
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
pub fn is_enterprise_store(&self) -> bool {
match self {
@ -730,6 +749,7 @@ impl Store {
_ => false,
}
}
// SPDX-SnippetEnd
#[cfg(not(feature = "enterprise"))]
pub fn is_enterprise_store(&self) -> bool {
@ -750,8 +770,13 @@ impl std::fmt::Debug for Store {
Self::MySQL(_) => f.debug_tuple("MySQL").finish(),
#[cfg(feature = "rocks")]
Self::RocksDb(_) => f.debug_tuple("RocksDb").finish(),
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(all(feature = "enterprise", any(feature = "postgres", feature = "mysql")))]
Self::SQLReadReplica(_) => f.debug_tuple("SQLReadReplica").finish(),
// SPDX-SnippetEnd
Self::None => f.debug_tuple("None").finish(),
}
}
@ -781,6 +806,9 @@ impl From<Value<'static>> for () {
impl Stores {
pub fn disable_enterprise_only(&mut self) {
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
{
#[cfg(any(feature = "postgres", feature = "mysql"))]
@ -789,5 +817,6 @@ impl Stores {
self.blob_stores
.retain(|_, store| !matches!(store.backend, BlobBackend::Sharded(_)));
}
// SPDX-SnippetEnd
}
}

View file

@ -21,5 +21,6 @@ compact_str = "0.9.0"
[features]
test_mode = []
enterprise = []
[dev-dependencies]

View file

@ -8,11 +8,9 @@
*
*/
use std::net::{Ipv4Addr, Ipv6Addr};
use compact_str::format_compact;
use crate::*;
use compact_str::format_compact;
use std::net::{Ipv4Addr, Ipv6Addr};
const VERSION: u8 = 1;

View file

@ -4,6 +4,12 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod binary;
pub mod json;
pub mod text;
// SPDX-SnippetBegin
// SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
// SPDX-License-Identifier: LicenseRef-SEL
#[cfg(feature = "enterprise")]
pub mod binary;
// SPDX-SnippetEnd

265
resources/scripts/ossify.py Normal file
View file

@ -0,0 +1,265 @@
#!/usr/bin/env python3
"""
Stalwart SEL code remover
This script removes SEL code from the Stalwart codebase by:
1. Removing entire .rs files that contain "SPDX-License-Identifier: LicenseRef-SEL" in their first comment
2. Removing SEL snippets marked with SPDX-SnippetBegin/End from mixed files
Usage: python ossify.py <stalwart_repository>/crates
"""
import os
import sys
import re
import argparse
from pathlib import Path
from typing import List, Tuple, Optional
def find_first_comment_block(content: str) -> Optional[str]:
"""
Find the first comment block in a Rust file.
Returns the comment content or None if no comment block is found.
"""
# Remove leading whitespace and find the first comment
lines = content.strip().split('\n')
if not lines:
return None
first_line = lines[0].strip()
# Check for block comment starting with /*
if first_line.startswith('/*'):
comment_lines = []
in_comment = True
for line in lines:
if in_comment:
comment_lines.append(line)
if '*/' in line:
break
return '\n'.join(comment_lines)
# Check for line comments starting with //
elif first_line.startswith('//'):
comment_lines = []
for line in lines:
stripped = line.strip()
if stripped.startswith('//'):
comment_lines.append(line)
elif stripped == '':
comment_lines.append(line) # Keep empty lines within comment block
else:
break # Stop at first non-comment, non-empty line
return '\n'.join(comment_lines)
return None
def should_remove_file(file_path: str) -> bool:
"""
Check if a .rs file should be completely removed based on its first comment.
Returns True if the file contains "SPDX-License-Identifier: LicenseRef-SEL" in the first comment.
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
first_comment = find_first_comment_block(content)
if first_comment and 'SPDX-License-Identifier: LicenseRef-SEL' in first_comment:
return True
except Exception as e:
print(f"Error reading file {file_path}: {e}")
return False
def remove_proprietary_snippets(content: str) -> Tuple[str, int]:
"""
Remove proprietary snippets from file content.
Returns tuple of (modified_content, number_of_snippets_removed)
"""
snippets_removed = 0
# Pattern to match SPDX snippets that contain LicenseRef-SEL
# We look for SPDX-SnippetBegin, then check if the snippet contains LicenseRef-SEL,
# and if so, remove everything until SPDX-SnippetEnd
lines = content.split('\n')
result_lines = []
i = 0
while i < len(lines):
line = lines[i]
# Check if this line starts a snippet
if '// SPDX-SnippetBegin' in line:
# Look ahead to see if this snippet contains LicenseRef-SEL
snippet_start = i
snippet_lines = []
j = i
# Collect the snippet lines until we find SnippetEnd or reach end of file
while j < len(lines):
snippet_lines.append(lines[j])
if '// SPDX-SnippetEnd' in lines[j]:
break
j += 1
# Check if this snippet contains LicenseRef-SEL
snippet_content = '\n'.join(snippet_lines)
if 'SPDX-License-Identifier: LicenseRef-SEL' in snippet_content:
# Remove this snippet
snippets_removed += 1
i = j + 1 # Skip past the SnippetEnd line
continue
else:
# Keep this snippet as it's not proprietary
result_lines.append(line)
i += 1
else:
result_lines.append(line)
i += 1
return '\n'.join(result_lines), snippets_removed
def process_rust_file(file_path: str, dry_run: bool = False) -> dict:
"""
Process a single Rust file, removing proprietary content.
Returns a dictionary with processing results.
"""
result = {
'file': file_path,
'action': 'none',
'snippets_removed': 0,
'error': None
}
try:
# Check if the entire file should be removed
if should_remove_file(file_path):
result['action'] = 'file_removed'
if not dry_run:
os.remove(file_path)
return result
# Process snippets in the file
with open(file_path, 'r', encoding='utf-8') as f:
original_content = f.read()
modified_content, snippets_removed = remove_proprietary_snippets(original_content)
if snippets_removed > 0:
result['action'] = 'snippets_removed'
result['snippets_removed'] = snippets_removed
if not dry_run:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(modified_content)
except Exception as e:
result['error'] = str(e)
return result
def find_rust_files(directory: str) -> List[str]:
"""Find all .rs files in the given directory recursively."""
rust_files = []
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith('.rs'):
rust_files.append(os.path.join(root, file))
return rust_files
def main():
parser = argparse.ArgumentParser(
description='Remove Enterprise licensed code from Stalwart codebase'
)
parser.add_argument(
'directory',
help='Directory containing Stalwart code to process'
)
parser.add_argument(
'--dry-run',
action='store_true',
help='Show what would be done without making changes'
)
parser.add_argument(
'--verbose',
action='store_true',
help='Show detailed output for each file'
)
args = parser.parse_args()
if not os.path.isdir(args.directory):
print(f"Error: {args.directory} is not a valid directory")
sys.exit(1)
print(f"Processing Rust files in: {args.directory}")
if args.dry_run:
print("DRY RUN MODE - No changes will be made")
print()
rust_files = find_rust_files(args.directory)
if not rust_files:
print("No .rs files found in the specified directory")
return
print(f"Found {len(rust_files)} Rust files")
print()
files_removed = 0
files_with_snippets_removed = 0
total_snippets_removed = 0
errors = []
for file_path in rust_files:
result = process_rust_file(file_path, args.dry_run)
if result['error']:
errors.append(f"{file_path}: {result['error']}")
continue
if result['action'] == 'file_removed':
files_removed += 1
if args.verbose or args.dry_run:
action_text = "Would remove" if args.dry_run else "Removed"
print(f"{action_text} file: {file_path}")
elif result['action'] == 'snippets_removed':
files_with_snippets_removed += 1
total_snippets_removed += result['snippets_removed']
if args.verbose or args.dry_run:
action_text = "Would remove" if args.dry_run else "Removed"
print(f"{action_text} {result['snippets_removed']} snippet(s) from: {file_path}")
# Summary
print("\nSummary:")
action_text = "Would be" if args.dry_run else "Were"
print(f"- {files_removed} files {action_text.lower()} completely removed")
print(f"- {total_snippets_removed} proprietary snippets {action_text.lower()} removed from {files_with_snippets_removed} files")
if errors:
print(f"- {len(errors)} errors occurred:")
for error in errors:
print(f" {error}")
if args.dry_run:
print("\nRun without --dry-run to apply changes")
if __name__ == '__main__':
main()

View file

@ -40,7 +40,7 @@ common = { path = "../crates/common", features = ["test_mode", "enterprise"] }
email = { path = "../crates/email", features = ["test_mode", "enterprise"] }
spam-filter = { path = "../crates/spam-filter", features = ["test_mode", "enterprise"] }
migration = { path = "../crates/migration", features = ["test_mode", "enterprise"] }
trc = { path = "../crates/trc" }
trc = { path = "../crates/trc", features = ["enterprise"] }
managesieve = { path = "../crates/managesieve", features = ["test_mode", "enterprise"] }
smtp-proto = { version = "0.1" }
mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] }