EE code reorganisation
This commit is contained in:
@@ -10,7 +10,6 @@ nlp = { path = "../nlp" }
|
||||
store = { path = "../store" }
|
||||
directory = { path = "../directory" }
|
||||
jmap_proto = { path = "../jmap-proto" }
|
||||
se_licensing = { path = "../se-licensing", optional = true }
|
||||
sieve-rs = { version = "0.5" }
|
||||
mail-parser = { version = "0.9", features = ["full_encoding", "ludicrous_mode"] }
|
||||
mail-auth = { version = "0.4" }
|
||||
@@ -67,4 +66,4 @@ tracing-journald = "0.3"
|
||||
|
||||
[features]
|
||||
test_mode = []
|
||||
enterprise = ["se_licensing"]
|
||||
enterprise = []
|
||||
|
||||
@@ -16,13 +16,6 @@ use crate::{
|
||||
Network,
|
||||
};
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use crate::Enterprise;
|
||||
#[cfg(feature = "enterprise")]
|
||||
use jmap_proto::types::collection::Collection;
|
||||
#[cfg(feature = "enterprise")]
|
||||
use se_licensing::license::LicenseValidator;
|
||||
|
||||
use self::{
|
||||
imap::ImapConfig, jmap::settings::JmapConfig, scripts::Scripting, smtp::SmtpConfig,
|
||||
storage::Storage,
|
||||
@@ -123,60 +116,6 @@ impl Core {
|
||||
.directories
|
||||
.insert("*".to_string(), directory.clone());
|
||||
|
||||
// SPDX-SnippetBegin
|
||||
// SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
// SPDX-License-Identifier: LicenseRef-SEL
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
let enterprise = match config.value("enterprise.license-key").map(|key| {
|
||||
LicenseValidator::new().try_parse(key).and_then(|key| {
|
||||
key.into_validated_key(config.value("lookup.default.hostname").unwrap_or_default())
|
||||
})
|
||||
}) {
|
||||
Some(Ok(license)) => {
|
||||
match data
|
||||
.get_bitmap(store::BitmapKey::document_ids(
|
||||
u32::MAX,
|
||||
Collection::Principal,
|
||||
))
|
||||
.await
|
||||
{
|
||||
Ok(Some(bitmap)) if bitmap.len() > license.accounts as u64 => {
|
||||
config.new_build_warning(
|
||||
"enterprise.license-key",
|
||||
format!(
|
||||
"License key is valid but only allows {} accounts, found {}.",
|
||||
license.accounts,
|
||||
bitmap.len()
|
||||
),
|
||||
);
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
if !matches!(data, Store::None) {
|
||||
config.new_build_error("enterprise.license-key", e.to_string());
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => Some(Enterprise {
|
||||
license,
|
||||
undelete_period: config
|
||||
.property_or_default::<Option<std::time::Duration>>(
|
||||
"enterprise.undelete-period",
|
||||
"false",
|
||||
)
|
||||
.unwrap_or_default(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
Some(Err(e)) => {
|
||||
config.new_build_warning("enterprise.license-key", e.to_string());
|
||||
None
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
// SPDX-SnippetEnd
|
||||
|
||||
// If any of the stores are missing, disable all stores to avoid data loss
|
||||
if matches!(data, Store::None)
|
||||
|| matches!(&blob.backend, BlobBackend::Store(Store::None))
|
||||
@@ -194,6 +133,8 @@ impl Core {
|
||||
}
|
||||
|
||||
Self {
|
||||
#[cfg(feature = "enterprise")]
|
||||
enterprise: crate::enterprise::Enterprise::parse(config, &data).await,
|
||||
sieve: Scripting::parse(config, &stores).await,
|
||||
network: Network::parse(config),
|
||||
smtp: SmtpConfig::parse(config).await,
|
||||
@@ -215,8 +156,6 @@ impl Core {
|
||||
blobs: stores.blob_stores,
|
||||
ftss: stores.fts_stores,
|
||||
},
|
||||
#[cfg(feature = "enterprise")]
|
||||
enterprise,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
64
crates/common/src/enterprise/config.rs
Normal file
64
crates/common/src/enterprise/config.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: LicenseRef-SEL
|
||||
*
|
||||
* This file is subject to the Stalwart Enterprise License Agreement (SEL) and
|
||||
* is NOT open source software.
|
||||
*
|
||||
*/
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use jmap_proto::types::collection::Collection;
|
||||
use store::{BitmapKey, Store};
|
||||
use utils::config::Config;
|
||||
|
||||
use super::{license::LicenseValidator, Enterprise};
|
||||
|
||||
impl Enterprise {
|
||||
pub async fn parse(config: &mut Config, data: &Store) -> Option<Self> {
|
||||
let license = match LicenseValidator::new()
|
||||
.try_parse(config.value("enterprise.license-key")?)
|
||||
.and_then(|key| {
|
||||
key.into_validated_key(config.value("lookup.default.hostname").unwrap_or_default())
|
||||
}) {
|
||||
Ok(key) => key,
|
||||
Err(err) => {
|
||||
config.new_build_warning("enterprise.license-key", err.to_string());
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
match data
|
||||
.get_bitmap(BitmapKey::document_ids(u32::MAX, Collection::Principal))
|
||||
.await
|
||||
{
|
||||
Ok(Some(bitmap)) if bitmap.len() > license.accounts as u64 => {
|
||||
config.new_build_warning(
|
||||
"enterprise.license-key",
|
||||
format!(
|
||||
"License key is valid but only allows {} accounts, found {}.",
|
||||
license.accounts,
|
||||
bitmap.len()
|
||||
),
|
||||
);
|
||||
return None;
|
||||
}
|
||||
Err(e) => {
|
||||
if !matches!(data, Store::None) {
|
||||
config.new_build_error("enterprise.license-key", e.to_string());
|
||||
}
|
||||
return None;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Some(Enterprise {
|
||||
license,
|
||||
undelete_period: config
|
||||
.property_or_default::<Option<Duration>>("enterprise.undelete-period", "false")
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
295
crates/common/src/enterprise/license.rs
Normal file
295
crates/common/src/enterprise/license.rs
Normal file
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: LicenseRef-SEL
|
||||
*
|
||||
* This file is subject to the Stalwart Enterprise License Agreement (SEL) and
|
||||
* is NOT open source software.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* WARNING: TAMPERING WITH THIS CODE IS STRICTLY PROHIBITED
|
||||
* Any attempt to modify, bypass, or disable the license validation mechanism
|
||||
* constitutes a severe violation of the Stalwart Enterprise License Agreement.
|
||||
* Such actions may result in immediate termination of your license, legal action,
|
||||
* and substantial financial penalties. Stalwart Labs Ltd. actively monitors for
|
||||
* unauthorized modifications and will pursue all available legal remedies against
|
||||
* violators to the fullest extent of the law, including but not limited to claims
|
||||
* for copyright infringement, breach of contract, and fraud.
|
||||
*/
|
||||
|
||||
use std::{
|
||||
fmt::{Display, Formatter},
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use ring::signature::{Ed25519KeyPair, UnparsedPublicKey, ED25519};
|
||||
|
||||
use base64::{engine::general_purpose::STANDARD, Engine};
|
||||
|
||||
pub struct LicenseValidator {
|
||||
public_key: UnparsedPublicKey<Vec<u8>>,
|
||||
}
|
||||
|
||||
pub struct LicenseGenerator {
|
||||
key_pair: Ed25519KeyPair,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LicenseKey {
|
||||
pub valid_to: u64,
|
||||
pub valid_from: u64,
|
||||
pub hostname: String,
|
||||
pub accounts: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LicenseError {
|
||||
Expired,
|
||||
HostnameMismatch { issued_to: String, current: String },
|
||||
Parse,
|
||||
Validation,
|
||||
Decode,
|
||||
InvalidParameters,
|
||||
}
|
||||
|
||||
const U64_LEN: usize = std::mem::size_of::<u64>();
|
||||
const U32_LEN: usize = std::mem::size_of::<u32>();
|
||||
|
||||
impl LicenseValidator {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
LicenseValidator {
|
||||
public_key: UnparsedPublicKey::new(
|
||||
&ED25519,
|
||||
vec![
|
||||
118, 10, 182, 35, 89, 111, 11, 60, 154, 47, 205, 127, 107, 229, 55, 104, 72,
|
||||
54, 141, 14, 97, 219, 2, 4, 119, 143, 156, 10, 152, 216, 32, 194,
|
||||
],
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_parse(&self, key: impl AsRef<str>) -> Result<LicenseKey, LicenseError> {
|
||||
let key = STANDARD
|
||||
.decode(key.as_ref())
|
||||
.map_err(|_| LicenseError::Decode)?;
|
||||
let valid_from = u64::from_le_bytes(
|
||||
key.get(..U64_LEN)
|
||||
.ok_or(LicenseError::Parse)?
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let valid_to = u64::from_le_bytes(
|
||||
key.get(U64_LEN..(U64_LEN * 2))
|
||||
.ok_or(LicenseError::Parse)?
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let accounts = u32::from_le_bytes(
|
||||
key.get((U64_LEN * 2)..(U64_LEN * 2) + U32_LEN)
|
||||
.ok_or(LicenseError::Parse)?
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let hostname_len = u32::from_le_bytes(
|
||||
key.get((U64_LEN * 2) + U32_LEN..(U64_LEN * 2) + (U32_LEN * 2))
|
||||
.ok_or(LicenseError::Parse)?
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
) as usize;
|
||||
let hostname = String::from_utf8(
|
||||
key.get((U64_LEN * 2) + (U32_LEN * 2)..(U64_LEN * 2) + (U32_LEN * 2) + hostname_len)
|
||||
.ok_or(LicenseError::Parse)?
|
||||
.to_vec(),
|
||||
)
|
||||
.map_err(|_| LicenseError::Parse)?;
|
||||
let signature = key
|
||||
.get((U64_LEN * 2) + (U32_LEN * 2) + hostname_len..)
|
||||
.ok_or(LicenseError::Parse)?;
|
||||
|
||||
if valid_from == 0
|
||||
|| valid_to == 0
|
||||
|| valid_from >= valid_to
|
||||
|| accounts == 0
|
||||
|| hostname.is_empty()
|
||||
{
|
||||
return Err(LicenseError::InvalidParameters);
|
||||
}
|
||||
|
||||
// Validate signature
|
||||
self.public_key
|
||||
.verify(
|
||||
&key[..(U64_LEN * 2) + (U32_LEN * 2) + hostname_len],
|
||||
signature,
|
||||
)
|
||||
.map_err(|_| LicenseError::Validation)?;
|
||||
|
||||
let key = LicenseKey {
|
||||
valid_from,
|
||||
valid_to,
|
||||
hostname,
|
||||
accounts,
|
||||
};
|
||||
|
||||
if !key.is_expired() {
|
||||
Ok(key)
|
||||
} else {
|
||||
Err(LicenseError::Expired)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LicenseKey {
|
||||
pub fn new(hostname: String, accounts: u32, expires_in: u64) -> Self {
|
||||
let now = SystemTime::UNIX_EPOCH
|
||||
.elapsed()
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
LicenseKey {
|
||||
valid_from: now - 300,
|
||||
valid_to: now + expires_in + 300,
|
||||
hostname,
|
||||
accounts,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expires_in(&self) -> Duration {
|
||||
Duration::from_secs(
|
||||
self.valid_to.saturating_sub(
|
||||
SystemTime::UNIX_EPOCH
|
||||
.elapsed()
|
||||
.unwrap_or_default()
|
||||
.as_secs(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_expired(&self) -> bool {
|
||||
let now = SystemTime::UNIX_EPOCH
|
||||
.elapsed()
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
now >= self.valid_to || now < self.valid_from
|
||||
}
|
||||
|
||||
pub fn into_validated_key(self, hostname: impl AsRef<str>) -> Result<Self, LicenseError> {
|
||||
if self.hostname != hostname.as_ref() {
|
||||
Err(LicenseError::HostnameMismatch {
|
||||
issued_to: self.hostname.clone(),
|
||||
current: hostname.as_ref().to_string(),
|
||||
})
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LicenseGenerator {
|
||||
pub fn new(pkcs8_der: impl AsRef<[u8]>) -> Self {
|
||||
Self {
|
||||
key_pair: Ed25519KeyPair::from_pkcs8(pkcs8_der.as_ref()).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate(&self, key: LicenseKey) -> String {
|
||||
let mut bytes = Vec::new();
|
||||
bytes.extend_from_slice(&key.valid_from.to_le_bytes());
|
||||
bytes.extend_from_slice(&key.valid_to.to_le_bytes());
|
||||
bytes.extend_from_slice(&key.accounts.to_le_bytes());
|
||||
bytes.extend_from_slice(&(key.hostname.len() as u32).to_le_bytes());
|
||||
bytes.extend_from_slice(key.hostname.as_bytes());
|
||||
bytes.extend_from_slice(self.key_pair.sign(&bytes).as_ref());
|
||||
STANDARD.encode(&bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LicenseError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LicenseError::Expired => write!(f, "License is expired"),
|
||||
LicenseError::Parse => write!(f, "Failed to parse license key"),
|
||||
LicenseError::Validation => write!(f, "Failed to validate license key"),
|
||||
LicenseError::Decode => write!(f, "Failed to decode license key"),
|
||||
LicenseError::InvalidParameters => write!(f, "Invalid license key parameters"),
|
||||
LicenseError::HostnameMismatch { issued_to, current } => {
|
||||
write!(
|
||||
f,
|
||||
"License issued to {} does not match {}",
|
||||
issued_to, current
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
use rustls::sign::CertifiedKey;
|
||||
use webpki::TrustAnchor;
|
||||
use x509_parser::{certificate::X509Certificate, prelude::FromDer};
|
||||
|
||||
|
||||
fn validate_certificate(key: &CertifiedKey) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cert_der = key.end_entity_cert()?.as_ref();
|
||||
|
||||
webpki::EndEntityCert::try_from(cert_der)?.verify_is_valid_tls_server_cert(
|
||||
&[
|
||||
&webpki::ECDSA_P256_SHA256,
|
||||
&webpki::ECDSA_P256_SHA384,
|
||||
&webpki::ECDSA_P384_SHA256,
|
||||
&webpki::ECDSA_P384_SHA384,
|
||||
&webpki::ED25519,
|
||||
&webpki::RSA_PKCS1_2048_8192_SHA256,
|
||||
&webpki::RSA_PKCS1_2048_8192_SHA384,
|
||||
&webpki::RSA_PKCS1_2048_8192_SHA512,
|
||||
&webpki::RSA_PKCS1_3072_8192_SHA384,
|
||||
&webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY,
|
||||
&webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY,
|
||||
&webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY,
|
||||
],
|
||||
&webpki::TlsServerTrustAnchors(
|
||||
webpki_roots::TLS_SERVER_ROOTS
|
||||
.iter()
|
||||
.map(|ta| TrustAnchor {
|
||||
subject: ta.subject.as_ref(),
|
||||
spki: ta.subject_public_key_info.as_ref(),
|
||||
name_constraints: ta.name_constraints.as_ref().map(|nc| nc.as_ref()),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
),
|
||||
&key.cert
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|der| der.as_ref())
|
||||
.collect::<Vec<_>>(),
|
||||
webpki::Time::try_from(SystemTime::now())?,
|
||||
)?;
|
||||
|
||||
// Additional checks
|
||||
let x509 = X509Certificate::from_der(cert_der)?.1;
|
||||
|
||||
// Check if self-signed
|
||||
if x509.issuer() == x509.subject() {
|
||||
return Err("Certificate is self-signed".into());
|
||||
}
|
||||
|
||||
// Check expiration
|
||||
let not_before = x509.validity().not_before.timestamp();
|
||||
let not_after = x509.validity().not_after.timestamp();
|
||||
let now = SystemTime::UNIX_EPOCH
|
||||
.elapsed()
|
||||
.unwrap_or_default()
|
||||
.as_secs() as i64;
|
||||
|
||||
if now < not_before || now > not_after {
|
||||
Err("Certificate is expired or not yet valid".into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
58
crates/common/src/enterprise/mod.rs
Normal file
58
crates/common/src/enterprise/mod.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: LicenseRef-SEL
|
||||
*
|
||||
* This file is subject to the Stalwart Enterprise License Agreement (SEL) and
|
||||
* is NOT open source software.
|
||||
*
|
||||
*/
|
||||
|
||||
pub mod config;
|
||||
pub mod license;
|
||||
pub mod undelete;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use license::LicenseKey;
|
||||
|
||||
use crate::Core;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Enterprise {
|
||||
pub license: LicenseKey,
|
||||
pub undelete_period: Option<Duration>,
|
||||
}
|
||||
|
||||
impl Core {
|
||||
// WARNING: TAMPERING WITH THIS FUNCTION IS STRICTLY PROHIBITED
|
||||
// Any attempt to modify, bypass, or disable this license validation mechanism
|
||||
// constitutes a severe violation of the Stalwart Enterprise License Agreement.
|
||||
// Such actions may result in immediate termination of your license, legal action,
|
||||
// and substantial financial penalties. Stalwart Labs Ltd. actively monitors for
|
||||
// unauthorized modifications and will pursue all available legal remedies against
|
||||
// violators to the fullest extent of the law, including but not limited to claims
|
||||
// for copyright infringement, breach of contract, and fraud.
|
||||
|
||||
pub fn is_enterprise_edition(&self) -> bool {
|
||||
self.enterprise
|
||||
.as_ref()
|
||||
.map_or(false, |e| !e.license.is_expired())
|
||||
}
|
||||
|
||||
pub fn licensed_accounts(&self) -> u32 {
|
||||
self.enterprise.as_ref().map_or(0, |e| e.license.accounts)
|
||||
}
|
||||
|
||||
pub fn log_license_details(&self) {
|
||||
if let Some(enterprise) = &self.enterprise {
|
||||
tracing::info!(
|
||||
licensed_to = enterprise.license.hostname,
|
||||
valid_from = enterprise.license.valid_from,
|
||||
valid_to = enterprise.license.valid_to,
|
||||
accounts = enterprise.license.accounts,
|
||||
"Stalwart Enterprise Edition license key is valid",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
114
crates/common/src/enterprise/undelete.rs
Normal file
114
crates/common/src/enterprise/undelete.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: LicenseRef-SEL
|
||||
*
|
||||
* This file is subject to the Stalwart Enterprise License Agreement (SEL) and
|
||||
* is NOT open source software.
|
||||
*
|
||||
*/
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use store::{
|
||||
write::{
|
||||
key::{DeserializeBigEndian, KeySerializer},
|
||||
now, BatchBuilder, BlobOp, ValueClass,
|
||||
},
|
||||
IterateParams, ValueKey, U32_LEN, U64_LEN,
|
||||
};
|
||||
use utils::{BlobHash, BLOB_HASH_LEN};
|
||||
|
||||
use crate::Core;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct DeletedBlob<H, T, C> {
|
||||
pub hash: H,
|
||||
pub size: usize,
|
||||
#[serde(rename = "deletedAt")]
|
||||
pub deleted_at: T,
|
||||
#[serde(rename = "expiresAt")]
|
||||
pub expires_at: T,
|
||||
pub collection: C,
|
||||
}
|
||||
|
||||
impl Core {
|
||||
pub fn hold_undelete(
|
||||
&self,
|
||||
batch: &mut BatchBuilder,
|
||||
collection: u8,
|
||||
blob_hash: &BlobHash,
|
||||
blob_size: usize,
|
||||
) {
|
||||
if let Some(hold_period) = self.enterprise.as_ref().and_then(|e| e.undelete_period) {
|
||||
let now = now();
|
||||
|
||||
batch.set(
|
||||
BlobOp::Reserve {
|
||||
hash: blob_hash.clone(),
|
||||
until: now + hold_period.as_secs(),
|
||||
},
|
||||
KeySerializer::new(U64_LEN + U64_LEN)
|
||||
.write(blob_size as u32)
|
||||
.write(now)
|
||||
.write(collection)
|
||||
.finalize(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_deleted(
|
||||
&self,
|
||||
account_id: u32,
|
||||
) -> store::Result<Vec<DeletedBlob<BlobHash, u64, u8>>> {
|
||||
let from_key = ValueKey {
|
||||
account_id,
|
||||
collection: 0,
|
||||
document_id: 0,
|
||||
class: ValueClass::Blob(BlobOp::Reserve {
|
||||
hash: BlobHash::default(),
|
||||
until: 0,
|
||||
}),
|
||||
};
|
||||
let to_key = ValueKey {
|
||||
account_id: account_id + 1,
|
||||
collection: 0,
|
||||
document_id: 0,
|
||||
class: ValueClass::Blob(BlobOp::Reserve {
|
||||
hash: BlobHash::default(),
|
||||
until: 0,
|
||||
}),
|
||||
};
|
||||
|
||||
let now = now();
|
||||
let mut results = Vec::new();
|
||||
|
||||
self.storage
|
||||
.data
|
||||
.iterate(
|
||||
IterateParams::new(from_key, to_key).ascending(),
|
||||
|key, value| {
|
||||
let expires_at = key.deserialize_be_u64(key.len() - U64_LEN)?;
|
||||
if value.len() == U32_LEN + U64_LEN + 1 && expires_at > now {
|
||||
results.push(DeletedBlob {
|
||||
hash: BlobHash::try_from_hash_slice(
|
||||
key.get(U32_LEN..U32_LEN + BLOB_HASH_LEN).ok_or_else(|| {
|
||||
store::Error::InternalError(format!(
|
||||
"Invalid key {key:?} in blob hash tables"
|
||||
))
|
||||
})?,
|
||||
)
|
||||
.unwrap(),
|
||||
size: value.deserialize_be_u32(0)? as usize,
|
||||
deleted_at: value.deserialize_be_u64(U32_LEN)?,
|
||||
expires_at,
|
||||
collection: *value.last().unwrap(),
|
||||
});
|
||||
}
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
}
|
||||
@@ -35,8 +35,6 @@ use opentelemetry_sdk::{
|
||||
Resource,
|
||||
};
|
||||
use opentelemetry_semantic_conventions::resource::{SERVICE_NAME, SERVICE_VERSION};
|
||||
#[cfg(feature = "enterprise")]
|
||||
use se_licensing::license::LicenseKey;
|
||||
use sieve::Sieve;
|
||||
use store::LookupStore;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
@@ -49,6 +47,8 @@ use webhooks::{manager::WebhookEvent, WebhookPayload, WebhookType, Webhooks};
|
||||
|
||||
pub mod addresses;
|
||||
pub mod config;
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub mod enterprise;
|
||||
pub mod expr;
|
||||
pub mod listener;
|
||||
pub mod manager;
|
||||
@@ -73,7 +73,7 @@ pub struct Core {
|
||||
pub imap: ImapConfig,
|
||||
pub web_hooks: Webhooks,
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub enterprise: Option<Enterprise>,
|
||||
pub enterprise: Option<enterprise::Enterprise>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -83,19 +83,6 @@ pub struct Network {
|
||||
pub url: IfBlock,
|
||||
}
|
||||
|
||||
// SPDX-SnippetBegin
|
||||
// SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
|
||||
// SPDX-License-Identifier: LicenseRef-SEL
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[derive(Clone)]
|
||||
pub struct Enterprise {
|
||||
pub license: LicenseKey,
|
||||
pub undelete_period: Option<std::time::Duration>,
|
||||
}
|
||||
|
||||
// SPDX-SnippetEnd
|
||||
|
||||
pub enum AuthResult<T> {
|
||||
Success(T),
|
||||
Failure(AuthFailureReason),
|
||||
|
||||
Reference in New Issue
Block a user