Import upstream v0.16.22, stripped

Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f
Enterprise-only files removed or emptied: 63
Enterprise-only snippets removed: 117 in 50 files
Dangling module declarations removed: 5
Cargo edits turning enterprise off: 14
Verification: clean
Enterprise feature gates left for rebuilt features: 19 in 18 files

Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
+435
View File
@@ -0,0 +1,435 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{Server, auth::DomainCache, cache::invalidate::CacheInvalidationBuilder};
use registry::{
schema::{
prelude::{Object, ObjectType},
structs::{
Account, Credential, EmailAlias, GroupAccount, PasswordCredential, Roles, UserAccount,
UserRoles,
},
},
types::{datetime::UTCDateTime, id::ObjectId, list::List},
};
use std::sync::Arc;
use store::registry::write::{RegistryWrite, RegistryWriteResult};
use trc::AddContext;
use types::id::Id;
pub struct AccountWithId {
pub id: u32,
pub account: Account,
}
impl Server {
pub async fn synchronize_account(
&self,
account: directory::Account,
) -> trc::Result<AccountWithId> {
let (local, domain) = self.validate_address(&account.email).await?;
match self
.account_id_from_parts(local, domain.id)
.await
.caused_by(trc::location!())?
{
Some(account_id) => {
let current_account = self
.registry()
.get(ObjectId::new(ObjectType::Account, account_id.into()))
.await
.caused_by(trc::location!())?
.ok_or_else(|| {
trc::AuthEvent::Error
.into_err()
.details("Account ID from directory does not exist in registry")
.ctx(trc::Key::AccountName, account.email.clone())
.ctx(trc::Key::AccountId, account_id)
})?;
let mut updated_account = Account::from(current_account.clone())
.into_user()
.ok_or_else(|| {
trc::AuthEvent::Error
.into_err()
.details(
"Account ID from directory does not correspond to a user account",
)
.ctx(trc::Key::AccountName, account.email.clone())
.ctx(trc::Key::AccountId, account_id)
})?;
let mut has_changes = false;
if let Some(secret) = account.secret
&& secret != updated_account.password().unwrap_or_default()
{
has_changes = true;
updated_account.set_password(secret);
}
if account.description.is_some()
&& account.description != updated_account.description
{
updated_account.description = account.description;
has_changes = true;
}
for alias in account.email_aliases {
if let Some((local, alias_domain)) = self.validate_alias(&alias).await?
&& alias_domain.id_tenant == domain.id_tenant
&& self
.rcpt_id_from_parts(local, alias_domain.id)
.await?
.is_none()
{
updated_account.aliases.push(EmailAlias {
name: local.to_string(),
domain_id: Id::from(alias_domain.id),
enabled: true,
description: None,
});
has_changes = true;
}
}
if let Some(groups) = account.groups {
let mut member_group_ids = Vec::with_capacity(groups.len());
for email in groups {
member_group_ids.push(
self.synchronize_group(directory::Group {
email,
..Default::default()
})
.await
.caused_by(trc::location!())?
.into(),
);
}
if updated_account.member_group_ids.len() != member_group_ids.len()
|| !updated_account
.member_group_ids
.iter()
.all(|id| member_group_ids.contains(id))
{
updated_account.member_group_ids = member_group_ids.into();
has_changes = true;
}
}
if has_changes {
let updated_account = Object::from(Account::User(updated_account));
match self
.registry()
.write(RegistryWrite::update(
Id::from(account_id),
&updated_account,
&current_account,
))
.await
.caused_by(trc::location!())?
{
RegistryWriteResult::Success(id) => {
let mut invalidator = CacheInvalidationBuilder::default();
invalidator.process_update(id, &current_account, &updated_account);
self.invalidate_caches(invalidator)
.await
.caused_by(trc::location!())?;
Ok(AccountWithId {
id: id.document_id(),
account: updated_account.into(),
})
}
failure => Err(trc::AuthEvent::Error
.into_err()
.caused_by(trc::location!())
.details("Failed to synchronize account with directory")
.reason(failure)),
}
} else {
Ok(AccountWithId {
id: account_id,
account: Account::User(updated_account),
})
}
}
None => {
let mut aliases = Vec::with_capacity(account.email_aliases.len());
for alias in account.email_aliases {
if let Some((local, alias_domain)) = self.validate_alias(&alias).await?
&& alias_domain.id_tenant == domain.id_tenant
&& self
.rcpt_id_from_parts(local, alias_domain.id)
.await?
.is_none()
{
aliases.push(EmailAlias {
name: local.to_string(),
domain_id: Id::from(alias_domain.id),
enabled: true,
description: None,
});
}
}
let mut member_group_ids = Vec::new();
for email in account.groups.unwrap_or_default() {
member_group_ids.push(
self.synchronize_group(directory::Group {
email,
..Default::default()
})
.await
.caused_by(trc::location!())?
.into(),
);
}
let account = Object::from(Account::User(UserAccount {
name: local.to_string(),
domain_id: Id::from(domain.id),
aliases: aliases.into(),
created_at: UTCDateTime::now(),
description: account.description,
member_group_ids: member_group_ids.into(),
member_tenant_id: domain.id_tenant.map(Id::from),
roles: UserRoles::User,
credentials: List::from_iter(account.secret.map(|secret| {
Credential::Password(PasswordCredential {
credential_id: 0u64.into(),
secret,
..Default::default()
})
})),
..Default::default()
}));
match self
.registry()
.write(RegistryWrite::insert(&account))
.await
.caused_by(trc::location!())?
{
RegistryWriteResult::Success(id) => {
let mut invalidator = CacheInvalidationBuilder::default();
invalidator.process_create(&account);
self.invalidate_caches(invalidator)
.await
.caused_by(trc::location!())?;
Ok(AccountWithId {
id: id.document_id(),
account: account.into(),
})
}
failure => Err(trc::AuthEvent::Error
.into_err()
.caused_by(trc::location!())
.details("Failed to create account from directory")
.reason(failure)),
}
}
}
}
pub async fn synchronize_group(&self, group: directory::Group) -> trc::Result<u32> {
let (local, domain) = self.validate_address(&group.email).await?;
match self
.account_id_from_parts(local, domain.id)
.await
.caused_by(trc::location!())?
{
Some(account_id) => {
let current_account = self
.registry()
.get(ObjectId::new(ObjectType::Account, account_id.into()))
.await
.caused_by(trc::location!())?
.ok_or_else(|| {
trc::AuthEvent::Error
.into_err()
.details("Account ID from directory does not exist in registry")
.ctx(trc::Key::AccountName, group.email.clone())
.ctx(trc::Key::AccountId, account_id)
})?;
let mut updated_account = Account::from(current_account.clone())
.into_group()
.ok_or_else(|| {
trc::AuthEvent::Error
.into_err()
.details(
"Account ID from directory does not correspond to a group account",
)
.ctx(trc::Key::AccountName, group.email.clone())
.ctx(trc::Key::AccountId, account_id)
})?;
let mut has_changes = false;
if group.description.is_some() && group.description != updated_account.description {
updated_account.description = group.description;
has_changes = true;
}
for alias in group.email_aliases {
if let Some((local, alias_domain)) = self.validate_alias(&alias).await?
&& alias_domain.id_tenant == domain.id_tenant
&& self
.rcpt_id_from_parts(local, alias_domain.id)
.await?
.is_none()
{
updated_account.aliases.push(EmailAlias {
name: local.to_string(),
domain_id: Id::from(alias_domain.id),
enabled: true,
description: None,
});
has_changes = true;
}
}
if has_changes {
let updated_account = Object::from(Account::Group(updated_account));
match self
.registry()
.write(RegistryWrite::update(
Id::from(account_id),
&updated_account,
&current_account,
))
.await
.caused_by(trc::location!())?
{
RegistryWriteResult::Success(id) => {
let mut invalidator = CacheInvalidationBuilder::default();
invalidator.process_update(id, &current_account, &updated_account);
self.invalidate_caches(invalidator)
.await
.caused_by(trc::location!())?;
Ok(id.document_id())
}
failure => Err(trc::AuthEvent::Error
.into_err()
.caused_by(trc::location!())
.details("Failed to synchronize account with directory")
.reason(failure)),
}
} else {
Ok(account_id)
}
}
None => {
let mut aliases = Vec::with_capacity(group.email_aliases.len());
for alias in group.email_aliases {
if let Some((local, alias_domain)) = self.validate_alias(&alias).await?
&& alias_domain.id_tenant == domain.id_tenant
&& self
.rcpt_id_from_parts(local, alias_domain.id)
.await?
.is_none()
{
aliases.push(EmailAlias {
name: local.to_string(),
domain_id: Id::from(alias_domain.id),
enabled: true,
description: None,
});
}
}
let account = Object::from(Account::Group(GroupAccount {
name: local.to_string(),
domain_id: Id::from(domain.id),
aliases: aliases.into(),
created_at: UTCDateTime::now(),
description: group.description,
member_tenant_id: domain.id_tenant.map(Id::from),
roles: Roles::Default,
..Default::default()
}));
match self
.registry()
.write(RegistryWrite::insert(&account))
.await
.caused_by(trc::location!())?
{
RegistryWriteResult::Success(id) => {
let mut invalidator = CacheInvalidationBuilder::default();
invalidator.process_create(&account);
self.invalidate_caches(invalidator)
.await
.caused_by(trc::location!())?;
Ok(id.document_id())
}
failure => Err(trc::AuthEvent::Error
.into_err()
.caused_by(trc::location!())
.details("Failed to create account from directory")
.reason(failure)),
}
}
}
}
async fn validate_address<'x>(
&self,
email: &'x str,
) -> trc::Result<(&'x str, Arc<DomainCache>)> {
if email.is_empty() {
return Err(trc::AuthEvent::Error
.into_err()
.details("Account email cannot be empty"));
}
match email.rsplit_once('@') {
Some((local, domain)) => self
.domain(domain)
.await
.caused_by(trc::location!())?
.map(|domain| (local, domain))
.ok_or_else(|| {
trc::AuthEvent::Error
.into_err()
.details("Account domain does not exist or has been disabled")
.ctx(trc::Key::Domain, domain.to_string())
}),
None => {
trc::event!(
Auth(trc::AuthEvent::Warning),
AccountName = email.to_string().clone(),
Details = "Directory account is not an email, appended default domain",
);
self.domain_by_id(self.core.email.default_domain_id)
.await
.caused_by(trc::location!())?
.ok_or_else(|| {
trc::AuthEvent::Error
.into_err()
.details("Default domain does not exist or has been disabled")
.ctx(trc::Key::Id, self.core.email.default_domain_id)
})
.map(|domain| (email, domain))
}
}
}
async fn validate_alias<'x>(
&self,
email: &'x str,
) -> trc::Result<Option<(&'x str, Arc<DomainCache>)>> {
match email.rsplit_once('@') {
Some((local, domain)) => self
.domain(domain)
.await
.caused_by(trc::location!())
.map(|domain| domain.map(|domain| (local, domain))),
None => Ok(None),
}
}
}
+440
View File
@@ -0,0 +1,440 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
Server,
auth::{EmailAddressRef, EmailCache},
ipc::{BroadcastEvent, CacheInvalidation},
};
use ahash::AHashSet;
use registry::{
schema::{
prelude::{Object, ObjectInner, ObjectType},
structs::{Account, EmailAlias},
},
types::id::ObjectId,
};
use store::{registry::RegistryQuery, roaring::RoaringBitmap};
use types::id::Id;
#[derive(Debug, Default)]
pub struct CacheInvalidationBuilder {
changes: AHashSet<CacheInvalidation>,
}
impl CacheInvalidationBuilder {
pub fn process_update(&mut self, id: Id, current_object: &Object, new_object: &Object) {
let id = id.document_id();
match (&current_object.inner, &new_object.inner) {
(
ObjectInner::Account(Account::User(current)),
ObjectInner::Account(Account::User(new)),
) => {
let was_renamed =
(current.name != new.name) || (current.domain_id != new.domain_id);
let quota_changed = current.quotas != new.quotas;
let permissions_changed = current.permissions != new.permissions;
let roles_changed = current.roles != new.roles;
let tenant_changed = current.member_tenant_id != new.member_tenant_id;
let details_changed =
current.locale != new.locale || current.description != new.description;
let groups_changed = current.member_group_ids != new.member_group_ids;
let aliases_changed = current.aliases != new.aliases;
let credentials_changed = current.credentials != new.credentials;
let encryption_changed = current.encryption_at_rest != new.encryption_at_rest;
if was_renamed
|| aliases_changed
|| tenant_changed
|| groups_changed
|| quota_changed
|| details_changed
|| encryption_changed
{
self.invalidate(CacheInvalidation::Account(id));
}
if was_renamed || aliases_changed {
self.invalidate_negative_email(&new_object.inner);
}
if tenant_changed
|| groups_changed
|| credentials_changed
|| roles_changed
|| permissions_changed
{
self.invalidate(CacheInvalidation::AccessToken(id));
}
if was_renamed {
self.invalidate(CacheInvalidation::DavResources(id));
}
}
(
ObjectInner::Account(Account::Group(current)),
ObjectInner::Account(Account::Group(new)),
) => {
let was_renamed =
(current.name != new.name) || (current.domain_id != new.domain_id);
let quota_changed = current.quotas != new.quotas;
let permissions_changed = current.permissions != new.permissions;
let roles_changed = current.roles != new.roles;
let tenant_changed = current.member_tenant_id != new.member_tenant_id;
let details_changed =
current.locale != new.locale || current.description != new.description;
let aliases_changed = current.aliases != new.aliases;
if was_renamed
|| aliases_changed
|| tenant_changed
|| quota_changed
|| details_changed
{
self.invalidate(CacheInvalidation::Account(id));
}
if was_renamed || aliases_changed {
self.invalidate_negative_email(&new_object.inner);
}
if tenant_changed || roles_changed || permissions_changed {
self.invalidate(CacheInvalidation::AccessToken(id));
}
if was_renamed {
self.invalidate(CacheInvalidation::DavResources(id));
}
}
(ObjectInner::Domain(current), ObjectInner::Domain(new)) => {
if (current.name != new.name)
|| (current.aliases != new.aliases)
|| (current.directory_id != new.directory_id)
|| (current.member_tenant_id != new.member_tenant_id)
|| (current.catch_all_address != new.catch_all_address)
|| (current.sub_addressing != new.sub_addressing)
|| (current.allow_relaying != new.allow_relaying)
|| (current.is_enabled != new.is_enabled)
{
self.invalidate(CacheInvalidation::Domain(id));
}
if (current.name != new.name) || (current.aliases != new.aliases) {
self.invalidate(CacheInvalidation::DomainNegative);
}
if current.logo != new.logo {
self.invalidate(CacheInvalidation::DomainLogo(id));
}
}
(ObjectInner::DkimSignature(current), ObjectInner::DkimSignature(new)) => {
let current_domain_id = current.domain_id().document_id();
let new_domain_id = new.domain_id().document_id();
self.invalidate(CacheInvalidation::DkimSignature(current_domain_id));
if current_domain_id != new_domain_id {
self.invalidate(CacheInvalidation::DkimSignature(new_domain_id));
}
}
(ObjectInner::Tenant(current), ObjectInner::Tenant(new)) => {
if (current.permissions != new.permissions)
|| (current.roles != new.roles)
|| (current.quotas != new.quotas)
{
self.invalidate(CacheInvalidation::Tenant(id));
}
if current.logo != new.logo {
self.invalidate(CacheInvalidation::TenantLogo(id));
}
}
(ObjectInner::Role(current), ObjectInner::Role(new))
if (current.enabled_permissions != new.enabled_permissions)
|| (current.disabled_permissions != new.disabled_permissions)
|| (current.member_tenant_id != new.member_tenant_id)
|| (current.role_ids != new.role_ids) =>
{
self.invalidate(CacheInvalidation::Role(id));
}
(ObjectInner::MailingList(current), ObjectInner::MailingList(new))
if (current.aliases != new.aliases)
|| (current.name != new.name)
|| (current.recipients != new.recipients)
|| (current.domain_id != new.domain_id) =>
{
self.invalidate(CacheInvalidation::List(id));
if (current.aliases != new.aliases)
|| (current.name != new.name)
|| (current.domain_id != new.domain_id)
{
self.invalidate_negative_email(&new_object.inner);
}
}
_ => {}
}
}
pub fn process_delete(&mut self, id: Id, object: &Object) {
let id = id.document_id();
match &object.inner {
ObjectInner::Account(_) => {
self.invalidate(CacheInvalidation::AccessToken(id));
self.invalidate(CacheInvalidation::Account(id));
self.invalidate(CacheInvalidation::DavResources(id));
}
ObjectInner::Domain(_) => {
self.invalidate(CacheInvalidation::Domain(id));
self.invalidate(CacheInvalidation::DomainLogo(id));
}
ObjectInner::DkimSignature(object) => {
self.invalidate(CacheInvalidation::DkimSignature(
object.domain_id().document_id(),
));
}
ObjectInner::Tenant(_) => {
self.invalidate(CacheInvalidation::Tenant(id));
self.invalidate(CacheInvalidation::TenantLogo(id));
}
ObjectInner::Role(_) => {
self.invalidate(CacheInvalidation::Role(id));
}
ObjectInner::MailingList(_) => {
self.invalidate(CacheInvalidation::List(id));
}
_ => {}
}
}
pub fn process_create(&mut self, object: &Object) {
if matches!(&object.inner, ObjectInner::Domain(_)) {
self.invalidate(CacheInvalidation::DomainNegative);
}
self.invalidate_negative_email(&object.inner);
}
fn invalidate_negative_email(&mut self, object: &ObjectInner) {
let (name, domain_id, aliases) = match object {
ObjectInner::Account(Account::User(account)) => {
(&account.name, account.domain_id, &account.aliases)
}
ObjectInner::Account(Account::Group(account)) => {
(&account.name, account.domain_id, &account.aliases)
}
ObjectInner::MailingList(list) => (&list.name, list.domain_id, &list.aliases),
_ => return,
};
self.invalidate(CacheInvalidation::EmailNegative {
domain_id: domain_id.document_id(),
local_part_hash: hash_local_part(name),
});
for alias in aliases.iter().filter(|alias: &&EmailAlias| alias.enabled) {
self.invalidate(CacheInvalidation::EmailNegative {
domain_id: alias.domain_id.document_id(),
local_part_hash: hash_local_part(&alias.name),
});
}
}
pub fn invalidate(&mut self, change: CacheInvalidation) {
self.changes.insert(change);
}
pub fn with_invalidation(mut self, change: CacheInvalidation) -> Self {
self.invalidate(change);
self
}
}
impl Server {
pub async fn invalidate_caches(&self, changes: CacheInvalidationBuilder) -> trc::Result<()> {
let mut changes = changes.changes;
if changes.is_empty() {
return Ok(());
}
// Invalidate objects linking roles
let mut role_ids = changes
.iter()
.filter_map(|change| {
if let CacheInvalidation::Role(role_id) = change {
Some(*role_id)
} else {
None
}
})
.collect::<Vec<_>>();
if !role_ids.is_empty() {
let mut fetched_role_ids = AHashSet::new();
while let Some(role_id) = role_ids.pop() {
if fetched_role_ids.insert(role_id) {
let linked_objects = self
.registry()
.linked_objects(ObjectId::new(ObjectType::Role, role_id.into()))
.await?;
for linked_object in linked_objects {
match linked_object.object() {
ObjectType::Account => {
changes.insert(CacheInvalidation::AccessToken(
linked_object.id().document_id(),
));
}
ObjectType::Role => {
role_ids.push(linked_object.id().document_id());
}
_ => {}
}
}
}
}
}
let changes = changes.into_iter().collect::<Vec<_>>();
self.invalidate_local_caches(&changes).await;
self.cluster_broadcast(BroadcastEvent::CacheInvalidate(changes))
.await;
Ok(())
}
pub fn invalidate_all_local_caches(&self) {
self.invalidate_all_local_negative_caches();
self.inner.cache.access_tokens.clear();
self.inner.cache.domains.clear();
self.inner.cache.domain_names.clear();
self.inner.cache.emails.clear();
self.inner.cache.tenants.clear();
self.inner.cache.files.clear();
self.inner.cache.contacts.clear();
self.inner.cache.events.clear();
self.inner.cache.scheduling.clear();
self.inner.cache.dkim_signers.clear();
self.inner.cache.accounts.clear();
self.inner.cache.roles.clear();
self.inner.cache.lists.clear();
self.inner.data.logos.lock().clear();
}
pub fn invalidate_all_local_negative_caches(&self) {
self.inner.cache.domain_names_negative.clear();
self.inner.cache.emails_negative.clear();
}
pub fn invalidate_local_negative_account_cache(
&self,
local_part: &str,
domain_id: u32,
) -> bool {
self.inner
.cache
.emails_negative
.remove(&EmailAddressRef::new(local_part, domain_id))
.is_some()
}
pub async fn invalidate_local_caches(&self, changes: &[CacheInvalidation]) {
let cache = &self.inner.cache;
let mut negative_emails: AHashSet<(u32, u32)> = AHashSet::new();
for change in changes {
match change {
CacheInvalidation::AccessToken(id) => {
cache.access_tokens.remove(id);
cache.http_auth.inner().retain(|_, v| v.account_id != *id);
}
CacheInvalidation::DavResources(id) => {
cache.files.remove(id);
cache.contacts.remove(id);
cache.events.remove(id);
cache.scheduling.remove(id);
}
CacheInvalidation::Domain(id) => {
cache.domains.remove(id);
cache.dkim_signers.remove(id);
cache.domain_names.inner().retain(|_, v| v != id);
}
CacheInvalidation::Account(id) => {
cache.accounts.remove(id);
cache.emails.inner().retain(|_, v| {
!matches!(
v,
EmailCache::Account(account_id)
| EmailCache::DisabledAccountAddress(account_id)
if account_id == id
)
});
}
CacheInvalidation::DkimSignature(id) => {
cache.dkim_signers.remove(id);
}
CacheInvalidation::Tenant(id) => {
cache.tenants.remove(id);
}
CacheInvalidation::Role(id) => {
cache.roles.remove(id);
}
CacheInvalidation::List(id) => {
cache.lists.remove(id);
cache.emails.inner().retain(|_, v| {
!matches!(
v,
EmailCache::MailingList(list_id)
| EmailCache::DisabledListAddress(list_id)
if list_id == id
)
});
}
CacheInvalidation::DomainLogo(id) => {
self.inner
.data
.logos
.lock()
.retain(|_, v| v.domain_id != *id);
}
CacheInvalidation::TenantLogo(id) => {
self.inner
.data
.logos
.lock()
.retain(|_, v| v.tenant_id != Some(*id));
}
CacheInvalidation::EmailNegative {
domain_id,
local_part_hash,
} => {
negative_emails.insert((*domain_id, *local_part_hash));
}
CacheInvalidation::DomainNegative => {
cache.domain_names_negative.clear();
}
}
}
if !negative_emails.is_empty() {
cache.emails_negative.retain(|key| {
!negative_emails.contains(&(key.domain_id, hash_local_part(&key.local_part)))
});
}
}
}
#[inline(always)]
fn hash_local_part(local_part: &str) -> u32 {
xxhash_rust::xxh3::xxh3_64(local_part.as_bytes()) as u32
}
impl From<CacheInvalidation> for CacheInvalidationBuilder {
fn from(invalidation: CacheInvalidation) -> Self {
let mut builder = CacheInvalidationBuilder::default();
builder.invalidate(invalidation);
builder
}
}
+94
View File
@@ -0,0 +1,94 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{DavResources, HttpAuthCache, MailboxCache, MessageStoreCache, UpdateLock};
use std::sync::atomic::{AtomicU64, Ordering};
use tokio::sync::{Semaphore, SemaphorePermit};
use utils::cache::CacheItemWeight;
pub mod directory;
pub mod invalidate;
pub mod principals;
pub mod reload;
impl MailboxCache {
pub fn parent_id(&self) -> Option<u32> {
if self.parent_id != u32::MAX {
Some(self.parent_id)
} else {
None
}
}
pub fn sort_order(&self) -> Option<u32> {
if self.sort_order != u32::MAX {
Some(self.sort_order)
} else {
None
}
}
pub fn is_root(&self) -> bool {
self.parent_id == u32::MAX
}
}
pub enum LockResult<'x> {
Acquired(SemaphorePermit<'x>),
Stale(SemaphorePermit<'x>),
}
impl UpdateLock {
pub fn new() -> Self {
Self {
semaphore: Semaphore::new(1),
revision: AtomicU64::new(0),
}
}
pub async fn acquire(&self, current_revision: u64) -> trc::Result<LockResult<'_>> {
let permit = self.semaphore.acquire().await.map_err(|err| {
trc::EventType::Server(trc::ServerEvent::ThreadError)
.reason(err)
.caused_by(trc::location!())
.details("Failed to acquire semaphore permit")
})?;
if self.revision.load(Ordering::Acquire) == current_revision {
Ok(LockResult::Acquired(permit))
} else {
Ok(LockResult::Stale(permit))
}
}
pub fn set_revision(&self, revision: u64) {
self.revision.store(revision, Ordering::Release);
}
}
impl Default for UpdateLock {
fn default() -> Self {
Self::new()
}
}
impl CacheItemWeight for MessageStoreCache {
fn weight(&self) -> u64 {
self.size
}
}
impl CacheItemWeight for HttpAuthCache {
fn weight(&self) -> u64 {
std::mem::size_of::<HttpAuthCache>() as u64
}
}
impl CacheItemWeight for DavResources {
fn weight(&self) -> u64 {
self.size
}
}
File diff suppressed because it is too large Load Diff
+182
View File
@@ -0,0 +1,182 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use crate::{
Core, Server,
config::{
server::{Listeners, tls::parse_certificates},
storage::Storage,
telemetry::Telemetry,
},
ipc::{QueueEvent, RegistryChange},
network::security::{BlockedIps, IpWithTtl},
};
use ahash::AHashMap;
use directory::Directories;
use registry::{
schema::{prelude::ObjectType, structs::BlockedIp},
types::error::{Error, Warning},
};
use std::sync::Arc;
use store::{LookupStores, registry::bootstrap::Bootstrap, write::now};
pub struct ReloadResult {
pub errors: Vec<Error>,
pub warnings: Vec<Warning>,
pub replaced_core: bool,
}
impl Server {
pub async fn reload_registry(&self, change: RegistryChange) -> trc::Result<ReloadResult> {
let mut bootstrap = Bootstrap::new(self.registry().clone()).await;
let object = match change {
RegistryChange::Insert(id) => {
if matches!(id.object(), ObjectType::BlockedIp) {
if let Some(ip) = bootstrap.get_infallible::<BlockedIp>(id.id()).await {
let expires_at = ip
.expires_at
.as_ref()
.map(|dt| dt.timestamp() as u64)
.unwrap_or(u64::MAX);
if expires_at > now() {
let mut ips = self.inner.data.blocked_ips.write();
if let Some(ip) = ip.address.try_to_ip() {
ips.blocked_ip_addresses
.insert(IpWithTtl::new(ip, expires_at));
} else {
ips.blocked_ip_networks
.push(IpWithTtl::new(ip.address, expires_at));
}
}
}
return Ok(bootstrap.into());
} else {
id.object()
}
}
RegistryChange::Delete(id) => id.object(),
RegistryChange::Reload(object) => object,
};
match object {
ObjectType::Certificate => {
let mut certificates = AHashMap::new();
parse_certificates(&mut bootstrap, &mut certificates, &mut Default::default())
.await;
self.inner
.data
.tls_certificates
.store(Arc::new(certificates));
}
ObjectType::MemoryLookupKey
| ObjectType::MemoryLookupKeyValue
| ObjectType::HttpLookup
| ObjectType::StoreLookup => {
let lookup = LookupStores::build(&mut bootstrap).await;
if bootstrap.errors.is_empty() {
self.inner.data.lookup_stores.store(Arc::new(lookup.stores));
}
}
ObjectType::BlockedIp => {
let blocked_ips = BlockedIps::parse(&mut bootstrap).await;
if bootstrap.errors.is_empty() {
*self.inner.data.blocked_ips.write() = blocked_ips;
}
}
ObjectType::Application => {
self.inner.data.applications.reload(&mut bootstrap).await;
if bootstrap.errors.is_empty() {
self.inner.data.applications.unpack_all(self, false).await;
}
}
_ => {
// Load stores
let directory = Directories::build(&mut bootstrap).await;
let storage = &self.core.storage;
let storage = Storage {
registry: storage.registry.clone(),
data: storage.data.clone(),
blob: storage.blob.clone(),
search: storage.search.clone(),
metrics: storage.metrics.clone(),
tracing: storage.tracing.clone(),
memory: storage.memory.clone(),
coordinator: storage.coordinator.clone(),
directory: directory.default_directory,
directories: directory.directories,
};
// Parse tracers
let tracers = Telemetry::parse(&mut bootstrap, &storage).await;
if bootstrap.errors.is_empty() {
let core = Box::pin(Core::parse(&mut bootstrap, storage)).await;
if bootstrap.errors.is_empty() {
let mut servers = Listeners::parse(&mut bootstrap).await;
servers
.parse_tcp_acceptors(&mut bootstrap, self.inner.clone())
.await;
if bootstrap.errors.is_empty() {
// Update core
self.inner.shared_core.store(core.into());
// Update tracers
#[cfg(not(feature = "enterprise"))]
tracers.update(false);
// Reload queue settings
self.inner
.ipc
.queue_tx
.send(QueueEvent::ReloadSettings)
.await
.ok();
return Ok(ReloadResult {
errors: bootstrap.errors,
warnings: bootstrap.warnings,
replaced_core: true,
});
}
}
}
}
}
Ok(bootstrap.into())
}
}
impl ReloadResult {
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn log(&self) {
for error in &self.errors {
error.log();
}
for warning in &self.warnings {
warning.log();
}
}
}
impl From<Bootstrap> for ReloadResult {
fn from(bootstrap: Bootstrap) -> Self {
Self {
errors: bootstrap.errors,
warnings: bootstrap.warnings,
replaced_core: false,
}
}
}