diff --git a/Cargo.lock b/Cargo.lock index 7a40cdcf..2b6e4d87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1090,7 +1090,7 @@ dependencies = [ "jmap_proto", "libc", "lz4_flex 0.12.0", - "mail-auth", + "mail-auth 0.8.0", "mail-builder", "mail-parser", "mail-send", @@ -2928,7 +2928,7 @@ dependencies = [ "hyper-util", "jmap", "jmap_proto", - "mail-auth", + "mail-auth 0.8.0", "mail-builder", "mail-parser", "mail-send", @@ -3597,7 +3597,7 @@ dependencies = [ "jmap-tools", "jmap_proto", "lz4_flex 0.12.0", - "mail-auth", + "mail-auth 0.8.0", "mail-builder", "mail-parser", "mail-send", @@ -4062,6 +4062,25 @@ dependencies = [ "mail-parser", "quick-xml 0.38.4", "quick_cache", + "ring", + "rustls-pki-types", + "serde", + "serde_json", + "zip", +] + +[[package]] +name = "mail-auth" +version = "0.8.0" +dependencies = [ + "ahash", + "flate2", + "hashify", + "hickory-resolver", + "mail-builder", + "mail-parser", + "quick-xml 0.39.0", + "quick_cache", "rand 0.8.5", "ring", "rkyv", @@ -4235,7 +4254,7 @@ dependencies = [ "futures", "groupware", "lz4_flex 0.12.0", - "mail-auth", + "mail-auth 0.8.0", "mail-parser", "nlp", "num_cpus", @@ -4567,9 +4586,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-integer" @@ -5524,6 +5543,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2e3bf4aa9d243beeb01a7b3bc30b77cfe2c44e24ec02d751a7104a53c2c49a1" +dependencies = [ + "memchr", +] + [[package]] name = "quick_cache" version = "0.6.18" @@ -7148,7 +7176,7 @@ dependencies = [ "hyper 1.8.1", "hyper-util", "lru-cache", - "mail-auth", + "mail-auth 0.8.0", "mail-builder", "mail-parser", "mail-send", @@ -7244,7 +7272,7 @@ dependencies = [ "hyper 1.8.1", "idna", "infer 0.19.0", - "mail-auth", + "mail-auth 0.8.0", "mail-builder", "mail-parser", "mail-send", @@ -7562,7 +7590,7 @@ dependencies = [ "jmap", "jmap-client", "jmap_proto", - "mail-auth", + "mail-auth 0.7.5", "mail-builder", "mail-parser", "mail-send", @@ -7646,9 +7674,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.46" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" dependencies = [ "deranged", "itoa", @@ -7662,15 +7690,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" [[package]] name = "time-macros" -version = "0.2.26" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" dependencies = [ "num-conv", "time-core", @@ -8093,7 +8121,7 @@ dependencies = [ "compact_str", "event_macro", "hashify", - "mail-auth", + "mail-auth 0.8.0", "mail-parser", "parking_lot", "reqwest", @@ -8357,6 +8385,7 @@ name = "utils" version = "0.15.4" dependencies = [ "ahash", + "arcstr", "base64 0.22.1", "blake3", "chrono", @@ -8366,7 +8395,7 @@ dependencies = [ "form_urlencoded", "futures", "http-body-util", - "mail-auth", + "mail-auth 0.8.0", "mail-send", "nohash-hasher", "parking_lot", diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 26c80b5c..d47cf2be 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -18,7 +18,7 @@ imap_proto = { path = "../imap-proto" } sieve-rs = { version = "0.7", features = ["rkyv", "serde"] } mail-parser = { version = "0.11", features = ["full_encoding"] } mail-builder = { version = "0.4" } -mail-auth = { version = "0.7.1" } +mail-auth = { path = "/Users/me/code/mail-auth" } mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } smtp-proto = { version = "0.2", features = ["rkyv"] } dns-update = { version = "0.1.5" } diff --git a/crates/common/src/addresses.rs b/crates/common/src/addresses.rs deleted file mode 100644 index 7aec8d11..00000000 --- a/crates/common/src/addresses.rs +++ /dev/null @@ -1,234 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL - */ - -use directory::Directory; -use registry::schema::enums::ExpressionVariable; -use std::borrow::Cow; - -use crate::{ - Server, - config::smtp::session::AddressMapping, - expr::{Variable, functions::ResolveVariable, if_block::IfBlock, tokenizer::TokenMap}, -}; - -impl Server { - pub async fn email_to_id( - &self, - directory: &Directory, - email: &str, - session_id: u64, - ) -> trc::Result> { - todo!() - - /*let mut address = self - .core - .smtp - .session - .rcpt - .subaddressing - .to_subaddress(self, email, session_id) - .await; - - for _ in 0..2 { - let result = directory.email_to_id(address.as_ref()).await?; - - if result.is_some() { - return Ok(result); - } else if let Some(catch_all) = self - .core - .smtp - .session - .rcpt - .catch_all - .to_catch_all(self, email, session_id) - .await - { - address = catch_all; - } else { - break; - } - } - - Ok(None)*/ - } - - pub async fn rcpt( - &self, - directory: &Directory, - email: &str, - session_id: u64, - ) -> trc::Result { - todo!() - - // Expand subaddress - /*let mut address = self - .core - .smtp - .session - .rcpt - .subaddressing - .to_subaddress(self, email, session_id) - .await; - - for _ in 0..2 { - let rcpt_type = directory.rcpt(address.as_ref()).await?; - if rcpt_type != RcptType::Invalid { - return Ok(rcpt_type); - } else if let Some(catch_all) = self - .core - .smtp - .session - .rcpt - .catch_all - .to_catch_all(self, email, session_id) - .await - { - address = catch_all; - } else { - break; - } - } - - Ok(RcptType::Invalid)*/ - } - - pub async fn vrfy( - &self, - directory: &Directory, - address: &str, - session_id: u64, - ) -> trc::Result> { - todo!() - - /*directory - .vrfy( - self.core - .smtp - .session - .rcpt - .subaddressing - .to_subaddress(self, address, session_id) - .await - .as_ref(), - ) - .await* */ - } - - pub async fn expn( - &self, - directory: &Directory, - address: &str, - session_id: u64, - ) -> trc::Result> { - todo!() - /*directory - .expn( - self.core - .smtp - .session - .rcpt - .subaddressing - .to_subaddress(self, address, session_id) - .await - .as_ref(), - ) - .await*/ - } -} - -/* -impl AddressMapping { - pub fn parse(bp: &mut Bootstrap, key: impl AsKey) -> Self { - let key = key.as_key(); - if let Some(value) = config.value(key.as_str()) { - match value { - "true" => AddressMapping::Enable, - "false" => AddressMapping::Disable, - _ => { - config.new_parse_error( - key, - format!("Invalid value for address mapping {value:?}",), - ); - AddressMapping::Disable - } - } - } else if let Some(if_block) = IfBlock::try_parse( - config, - key, - &TokenMap::default().with_variables_map([ - ("address", ExpressionVariable::Rcpt), - ("email", ExpressionVariable::Rcpt), - ("rcpt", ExpressionVariable::Rcpt), - ]), - ) { - AddressMapping::Custom(if_block) - } else { - AddressMapping::Enable - } - } -} - -struct Address<'x>(&'x str); - -impl ResolveVariable for Address<'_> { - fn resolve_variable(&'_ self, _: ExpressionVariable) -> crate::expr::Variable<'_> { - Variable::from(self.0) - } - - fn resolve_global(&self, _: &str) -> Variable<'_> { - Variable::Integer(0) - } -} - -impl AddressMapping { - pub async fn to_subaddress<'x, 'y: 'x>( - &'x self, - core: &Server, - address: &'y str, - session_id: u64, - ) -> Cow<'x, str> { - match self { - AddressMapping::Enable => { - if let Some((local_part, domain_part)) = address.rsplit_once('@') - && let Some((local_part, _)) = local_part.split_once('+') - { - return format!("{}@{}", local_part, domain_part).into(); - } - } - AddressMapping::Custom(if_block) => { - if let Some(result) = core - .eval_if::(if_block, &Address(address), session_id) - .await - { - return result.into(); - } - } - AddressMapping::Disable => (), - } - - address.into() - } - - pub async fn to_catch_all<'x, 'y: 'x>( - &'x self, - core: &Server, - address: &'y str, - session_id: u64, - ) -> Option> { - match self { - AddressMapping::Enable => address - .rsplit_once('@') - .map(|(_, domain_part)| format!("@{}", domain_part)) - .map(Cow::Owned), - AddressMapping::Custom(if_block) => core - .eval_if::(if_block, &Address(address), session_id) - .await - .map(Cow::Owned), - AddressMapping::Disable => None, - } - } -} -*/ diff --git a/crates/common/src/auth/access_token.rs b/crates/common/src/auth/access_token.rs index 42f286f4..4873b216 100644 --- a/crates/common/src/auth/access_token.rs +++ b/crates/common/src/auth/access_token.rs @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use super::{AccessToken, ResourceToken, TenantInfo, roles::PermissionsGroup}; +use super::AccessToken; use crate::{ Server, ipc::BroadcastEvent, @@ -24,11 +24,6 @@ use utils::map::{ vec_map::VecMap, }; -pub enum PrincipalOrId { - Principal(Principal), - Id(u32), -} - impl Server { async fn build_access_token_from_principal( &self, @@ -141,7 +136,7 @@ impl Server { // Build access token let mut access_token = AccessToken { - primary_id: principal.id, + account_id: principal.id, member_of, access_to: VecMap::new(), tenant, @@ -167,7 +162,7 @@ impl Server { revision, }; - for grant_account_id in [access_token.primary_id] + for grant_account_id in [access_token.account_id] .into_iter() .chain(access_token.member_of.iter().copied()) { @@ -375,31 +370,10 @@ impl Server { } } -impl From for PrincipalOrId { - fn from(id: u32) -> Self { - Self::Id(id) - } -} - -impl From for PrincipalOrId { - fn from(principal: Principal) -> Self { - Self::Principal(principal) - } -} - -impl PrincipalOrId { - pub fn id(&self) -> u32 { - match self { - Self::Principal(principal) => principal.id(), - Self::Id(id) => *id, - } - } -} - impl AccessToken { - pub fn from_id(primary_id: u32) -> Self { + pub fn from_id(account_id: u32) -> Self { Self { - primary_id, + account_id, ..Default::default() } } @@ -427,8 +401,8 @@ impl AccessToken { } #[inline(always)] - pub fn primary_id(&self) -> u32 { - self.primary_id + pub fn account_id(&self) -> u32 { + self.account_id } #[inline(always)] @@ -443,20 +417,20 @@ impl AccessToken { } pub fn member_ids(&self) -> impl Iterator { - [self.primary_id] + [self.account_id] .into_iter() .chain(self.member_of.iter().copied()) } pub fn all_ids(&self) -> impl Iterator { - [self.primary_id] + [self.account_id] .into_iter() .chain(self.member_of.iter().copied()) .chain(self.access_to.iter().map(|(id, _)| *id)) } pub fn all_ids_by_collection(&self, collection: Collection) -> impl Iterator { - [self.primary_id] + [self.account_id] .into_iter() .chain(self.member_of.iter().copied()) .chain(self.access_to.iter().filter_map(move |(id, cols)| { @@ -469,13 +443,13 @@ impl AccessToken { } pub fn is_member(&self, account_id: u32) -> bool { - self.primary_id == account_id + self.account_id == account_id || self.member_of.contains(&account_id) || self.has_permission(Permission::Impersonate) } - pub fn is_primary_id(&self, account_id: u32) -> bool { - self.primary_id == account_id + pub fn is_account_id(&self, account_id: u32) -> bool { + self.account_id == account_id } #[inline(always)] @@ -549,7 +523,7 @@ impl AccessToken { pub fn as_resource_token(&self) -> ResourceToken { ResourceToken { - account_id: self.primary_id, + account_id: self.account_id, quota: self.quota, tenant: self.tenant, } diff --git a/crates/common/src/auth/mod.rs b/crates/common/src/auth/mod.rs index ad213b19..5de0e79b 100644 --- a/crates/common/src/auth/mod.rs +++ b/crates/common/src/auth/mod.rs @@ -5,15 +5,13 @@ */ use crate::{expr::if_block::IfBlock, listener::limiter::ConcurrencyLimiter}; -use ahash::AHashMap; -use arc_swap::ArcSwap; use arcstr::ArcStr; use directory::Credentials; use registry::{ - schema::enums::{Locale, Permission, StorageObject}, + schema::enums::{Locale, Permission, StorageQuota, TenantStorageQuota}, types::EnumType, }; -use std::{collections::HashMap, net::IpAddr, sync::Arc}; +use std::net::IpAddr; use tinyvec::TinyVec; use trc::ipc::bitset::Bitset; use types::collection::Collection; @@ -22,144 +20,88 @@ use utils::{cache::CacheItemWeight, map::bitmap::Bitmap}; pub mod access_token; pub mod authentication; pub mod oauth; +pub mod permissions; pub mod rate_limit; -pub mod roles; pub mod sasl; const PERMISSIONS_BITSET_SIZE: usize = Permission::COUNT.div_ceil(std::mem::size_of::()); pub type Permissions = Bitset; -pub type ObjectQuota = [u32; StorageObject::COUNT - 1]; -pub type IdMap = HashMap>; +pub type ObjectQuota = [u32; StorageQuota::COUNT - 1]; +pub type TenantQuota = [u32; TenantStorageQuota::COUNT - 1]; +//pub type IdMap = HashMap, nohash_hasher::BuildNoHashHasher>; +//pub type NameMap = AHashMap>; -pub struct DirectoryEntries { - pub emails: ArcSwap, - pub domains: ArcSwap, - pub accounts: ArcSwap, - pub groups: ArcSwap, - pub roles: ArcSwap, - pub mailing_lists: ArcSwap, - pub tenants: ArcSwap, - pub api_keys: ArcSwap, +#[derive(Debug, Clone, Copy)] +pub enum EmailCache { + Account(u32), + Group(u32), + MailingList(u32), } #[derive(Debug, Clone)] -pub struct EmailEntries { - pub addresses: AHashMap, -} - -#[derive(Debug, Clone)] -pub struct EmailEntry { - pub id: u32, - pub flags: u8, -} - -pub const EMAIL_FLAG_ACCOUNT: u8 = 1; -pub const EMAIL_FLAG_GROUP: u8 = 1 << 1; -pub const EMAIL_FLAG_MAILING_LIST: u8 = 1 << 2; -pub const EMAIL_FLAG_ALIAS: u8 = 1 << 3; -pub const EMAIL_FLAG_EXPIRES: u8 = 1 << 4; - -#[derive(Debug, Clone)] -pub struct DomainEntries { - pub names: AHashMap, - pub entries: IdMap, - pub default: u32, -} - -#[derive(Debug, Clone)] -pub struct AccountEntries { - pub entries: IdMap, -} - -#[derive(Debug, Clone)] -pub struct GroupEntries { - pub entries: IdMap, -} - -#[derive(Debug, Clone)] -pub struct RoleEntries { - pub entries: IdMap, -} - -#[derive(Debug, Clone)] -pub struct MailingListEntries { - pub entries: IdMap, -} - -#[derive(Debug, Clone)] -pub struct TenantEntries { - pub entries: IdMap, -} - -#[derive(Debug, Clone)] -pub struct ApiKeyEntries { - pub entries: AHashMap, -} - -#[derive(Debug, Clone)] -pub struct DomainEntry { +pub struct DomainCache { pub name: ArcStr, - pub id_alias_of: u32, pub id_tenant: u32, pub id_directory: u32, - pub catch_all: Option, - pub sub_addressing_custom: Option>, + pub catch_all: Option>, + pub sub_addressing_custom: Option>, pub flags: u8, } -pub const DOMAIN_FLAG_LOCAL: u8 = 1; -pub const DOMAIN_FLAG_DEFAULT: u8 = 1 << 1; +pub const DOMAIN_FLAG_REMOTE: u8 = 1; +pub const DOMAIN_FLAG_SYSTEM: u8 = 1 << 1; pub const DOMAIN_FLAG_SUB_ADDRESSING: u8 = 1 << 2; pub const DOMAIN_FLAG_WILDCARD: u8 = 1 << 3; pub const DOMAIN_FLAG_ALIAS_LOGIN: u8 = 1 << 4; #[derive(Debug, Clone)] -pub struct AccountEntry { - pub addresses: Arc<[ArcStr]>, - pub id_tenant: u32, - pub description: Option, - pub locale: Locale, -} - -#[derive(Debug, Clone)] -pub struct GroupEntry { - pub addresses: Arc<[ArcStr]>, +pub struct AccountCache { + pub addresses: Box<[ArcStr]>, + pub addresses_temporary: Box<[TemporaryAddress]>, pub id_member_of: TinyVec<[u32; 3]>, pub id_tenant: u32, pub id_roles: TinyVec<[u32; 3]>, pub quota_disk: u64, - pub quota_objects: Option>, - pub permissions: Option>, + pub quota_objects: Option>, + pub description: Option>, + pub locale: Locale, } #[derive(Debug, Clone)] -pub struct RoleEntry { +pub struct TemporaryAddress { + pub address: ArcStr, + pub expires_at: u64, +} + +#[derive(Debug, Clone)] +pub struct RoleCache { pub id_tenant: u32, pub id_roles: TinyVec<[u32; 3]>, - pub permissions: Permissions, + pub permissions: PermissionsGroup, } #[derive(Debug, Clone)] -pub struct MailingListEntry { - pub addresses: Arc<[ArcStr]>, +pub struct MailingListCache { + pub addresses: Box<[ArcStr]>, + pub addresses_temporary: Box<[TemporaryAddress]>, pub id_tenant: u32, - pub recipients: Arc<[ArcStr]>, + pub recipients: Box<[ArcStr]>, } #[derive(Debug, Clone)] -pub struct TenantEntry { +pub struct TenantCache { pub id_roles: TinyVec<[u32; 3]>, pub quota_disk: u64, - pub quota_objects: Option>, - pub permissions: Option>, + pub quota_objects: Option>, + pub permissions: Option>, } #[derive(Debug, Clone)] -pub struct ApiKeyEntry { +pub struct ApiKeyCache { pub id: u32, pub id_tenant: u32, pub id_roles: TinyVec<[u32; 3]>, - pub permissions: Option>, + pub permissions: Option>, pub expires_at: u64, } @@ -172,14 +114,8 @@ pub struct PermissionsGroup { #[derive(Debug, Default)] pub struct AccessToken { - pub addresses: Arc<[ArcStr]>, - pub primary_id: u32, - pub member_of: TinyVec<[u32; 3]>, + pub account_id: u32, pub access_to: Box<[AccessTo]>, - pub quota_disk: u64, - pub quota_disk_tenant: u64, - pub quota_disk_domain: u64, - pub quota_objects: ObjectQuota, pub permissions: Permissions, pub concurrent_http_requests: Option, pub concurrent_imap_requests: Option, @@ -207,3 +143,63 @@ impl CacheItemWeight for AccessToken { self.obj_size } } + +impl CacheItemWeight for EmailCache { + fn weight(&self) -> u64 { + std::mem::size_of::() as u64 + } +} + +impl CacheItemWeight for DomainCache { + fn weight(&self) -> u64 { + std::mem::size_of::() as u64 + + self.name.len() as u64 + + self.catch_all.as_ref().map_or(0, |s| s.len() as u64) + + self + .sub_addressing_custom + .as_ref() + .map_or(0, |s| s.weight()) + } +} + +impl CacheItemWeight for AccountCache { + fn weight(&self) -> u64 { + std::mem::size_of::() as u64 + + self.addresses.iter().map(|s| s.len() as u64).sum::() + + self.description.as_ref().map_or(0, |s| s.len() as u64) + } +} + +impl CacheItemWeight for RoleCache { + fn weight(&self) -> u64 { + std::mem::size_of::() as u64 + } +} + +impl CacheItemWeight for MailingListCache { + fn weight(&self) -> u64 { + std::mem::size_of::() as u64 + + self.addresses.iter().map(|s| s.len() as u64).sum::() + + self.recipients.iter().map(|s| s.len() as u64).sum::() + } +} + +impl CacheItemWeight for TenantCache { + fn weight(&self) -> u64 { + std::mem::size_of::() as u64 + + self.permissions.as_ref().map_or(0, |p| p.weight()) + } +} + +impl CacheItemWeight for PermissionsGroup { + fn weight(&self) -> u64 { + std::mem::size_of::() as u64 + } +} + +impl CacheItemWeight for ApiKeyCache { + fn weight(&self) -> u64 { + std::mem::size_of::() as u64 + + self.permissions.as_ref().map_or(0, |p| p.weight()) + } +} diff --git a/crates/common/src/auth/oauth/config.rs b/crates/common/src/auth/oauth/config.rs index ad2f279f..35573d3b 100644 --- a/crates/common/src/auth/oauth/config.rs +++ b/crates/common/src/auth/oauth/config.rs @@ -17,7 +17,7 @@ use biscuit::{ }, jws::Secret, }; -use registry::schema::{enums::JwtSignatureAlgorithm, prelude::Object, structs::Authentication}; +use registry::schema::{enums::JwtSignatureAlgorithm, prelude::Object, structs::OidcProvider}; use ring::signature::{self, KeyPair}; use rsa::{RsaPublicKey, pkcs1::DecodeRsaPublicKey, traits::PublicKeyParts}; use store::{ @@ -47,7 +47,7 @@ pub struct OAuthConfig { impl OAuthConfig { pub async fn parse(bp: &mut Bootstrap) -> Self { - let auth = bp.setting_infallible::().await; + let auth = bp.setting_infallible::().await; let oidc_signature_algorithm = match auth.signature_algorithm { JwtSignatureAlgorithm::Es256 => SignatureAlgorithm::ES256, @@ -88,7 +88,7 @@ impl OAuthConfig { | SignatureAlgorithm::PS384 | SignatureAlgorithm::PS512 => parse_rsa_key(&auth) .map_err(|err| { - bp.build_error(Object::Authentication.singleton(), err); + bp.build_error(Object::OidcProvider.singleton(), err); }) .unwrap_or_else(|_| { ( @@ -102,7 +102,7 @@ impl OAuthConfig { SignatureAlgorithm::ES256 | SignatureAlgorithm::ES384 | SignatureAlgorithm::ES512 => { parse_ecdsa_key(&auth, oidc_signature_algorithm) .map_err(|err| { - bp.build_error(Object::Authentication.singleton(), err); + bp.build_error(Object::OidcProvider.singleton(), err); }) .unwrap_or_else(|_| { ( @@ -177,7 +177,7 @@ impl Default for OAuthConfig { } } -fn parse_rsa_key(auth: &Authentication) -> Result<(Secret, AlgorithmParameters), String> { +fn parse_rsa_key(auth: &OidcProvider) -> Result<(Secret, AlgorithmParameters), String> { let rsa_key_pair = build_rsa_keypair(&auth.signature_key)?; let rsa_public_key = match RsaPublicKey::from_pkcs1_der(rsa_key_pair.public_key().as_ref()) { @@ -201,7 +201,7 @@ fn parse_rsa_key(auth: &Authentication) -> Result<(Secret, AlgorithmParameters), } fn parse_ecdsa_key( - auth: &Authentication, + auth: &OidcProvider, oidc_signature_algorithm: SignatureAlgorithm, ) -> Result<(Secret, AlgorithmParameters), String> { let (alg, curve) = match oidc_signature_algorithm { diff --git a/crates/common/src/auth/oauth/introspect.rs b/crates/common/src/auth/oauth/introspect.rs index 293570e8..89cdbdb8 100644 --- a/crates/common/src/auth/oauth/introspect.rs +++ b/crates/common/src/auth/oauth/introspect.rs @@ -57,7 +57,7 @@ impl Server { Ok(token_info) => Ok(OAuthIntrospect { active: true, client_id: Some(token_info.client_id), - username: if access_token.primary_id() == token_info.account_id { + username: if access_token.account_id() == token_info.account_id { access_token.name.clone() } else { self.get_access_token(token_info.account_id) diff --git a/crates/common/src/auth/permissions.rs b/crates/common/src/auth/permissions.rs new file mode 100644 index 00000000..e5e87640 --- /dev/null +++ b/crates/common/src/auth/permissions.rs @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use crate::{ + Server, + auth::{Permissions, PermissionsGroup}, +}; +use ahash::AHashSet; +use trc::AddContext; + +impl Server { + pub async fn effective_permissions( + &self, + mut base_permissions: PermissionsGroup, + role_id: u32, + ) -> trc::Result { + let mut role_ids = vec![role_id]; + let mut fetched_role_ids = AHashSet::new(); + + while let Some(role_id) = role_ids.pop() { + if fetched_role_ids.insert(role_id) { + let role = self.role(role_id).await.caused_by(trc::location!())?; + + base_permissions.union(&role.permissions); + role_ids.extend(role.id_roles.iter().copied()); + } + } + + Ok(base_permissions.finalize()) + } +} + +impl PermissionsGroup { + pub fn union(&mut self, other: &PermissionsGroup) { + self.enabled.union(&other.enabled); + self.disabled.union(&other.disabled); + } + + pub fn finalize(mut self) -> Permissions { + self.enabled.difference(&self.disabled); + self.enabled + } + + pub fn finalize_as_ref(&self) -> Permissions { + let mut enabled = self.enabled.clone(); + enabled.difference(&self.disabled); + enabled + } +} diff --git a/crates/common/src/auth/rate_limit.rs b/crates/common/src/auth/rate_limit.rs index 5a637799..80d17249 100644 --- a/crates/common/src/auth/rate_limit.rs +++ b/crates/common/src/auth/rate_limit.rs @@ -21,10 +21,10 @@ impl Server { let is_rate_allowed = if let Some(rate) = &self.core.network.http.rate_authenticated { self.core .storage - .lookup + .memory .is_rate_allowed( KV_RATE_LIMIT_HTTP_AUTHENTICATED, - &access_token.primary_id.to_be_bytes(), + &access_token.account_id.to_be_bytes(), rate, false, ) @@ -60,7 +60,7 @@ impl Server { && self .core .storage - .lookup + .memory .is_rate_allowed( KV_RATE_LIMIT_HTTP_ANONYMOUS, &ip_to_bytes(addr), diff --git a/crates/common/src/auth/roles.rs b/crates/common/src/auth/roles.rs deleted file mode 100644 index 5f1ff422..00000000 --- a/crates/common/src/auth/roles.rs +++ /dev/null @@ -1,154 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL - */ - -use crate::{ - Server, - auth::{Permissions, PermissionsGroup}, -}; -use ahash::AHashSet; -use std::sync::{Arc, LazyLock}; -use trc::AddContext; -use utils::cache::CacheItemWeight; - -impl Server { - pub async fn get_role_permissions(&self, role_id: u32) -> trc::Result> { - match role_id { - ROLE_USER => Ok(USER_PERMISSIONS.clone()), - ROLE_ADMIN => Ok(ADMIN_PERMISSIONS.clone()), - ROLE_TENANT_ADMIN => Ok(TENANT_ADMIN_PERMISSIONS.clone()), - role_id => { - match self - .inner - .cache - .permissions - .get_value_or_guard_async(&role_id) - .await - { - Ok(permissions) => Ok(permissions), - Err(guard) => { - let permissions = self.build_role_permissions(role_id).await?; - let _ = guard.insert(permissions.clone()); - Ok(permissions) - } - } - } - } - } - - async fn build_role_permissions(&self, role_id: u32) -> trc::Result> { - let mut role_ids = vec![role_id].into_iter(); - let mut role_ids_stack = vec![]; - let mut fetched_role_ids = AHashSet::new(); - let mut return_permissions = PermissionsGroup::default(); - - 'outer: loop { - if let Some(role_id) = role_ids.next() { - // Skip if already fetched - if !fetched_role_ids.insert(role_id) { - continue; - } - - match role_id { - ROLE_USER => { - return_permissions.enabled.union(&USER_PERMISSIONS.enabled); - return_permissions - .disabled - .union(&USER_PERMISSIONS.disabled); - } - ROLE_ADMIN => { - return_permissions.enabled.union(&ADMIN_PERMISSIONS.enabled); - return_permissions - .disabled - .union(&ADMIN_PERMISSIONS.disabled); - break 'outer; - } - ROLE_TENANT_ADMIN => { - return_permissions - .enabled - .union(&TENANT_ADMIN_PERMISSIONS.enabled); - return_permissions - .disabled - .union(&TENANT_ADMIN_PERMISSIONS.disabled); - } - role_id => { - // Try with the cache - if let Some(role_permissions) = self.inner.cache.permissions.get(&role_id) { - return_permissions.union(role_permissions.as_ref()); - } else { - let mut role_permissions = PermissionsGroup::default(); - - // Obtain principal - let principal = self - .store() - .query(QueryParams::id(role_id).with_return_member_of(true)) - .await - .caused_by(trc::location!())? - .ok_or_else(|| { - trc::SecurityEvent::Unauthorized - .into_err() - .details( - "Principal not found while building role permissions", - ) - .ctx(trc::Key::Id, role_id) - })?; - - // Add permissions - for permission in principal.permissions() { - if permission.grant { - role_permissions.enabled.set(permission.permission as usize); - } else { - role_permissions - .disabled - .set(permission.permission as usize); - } - } - - // Add permissions - return_permissions.union(&role_permissions); - - // Add parent roles - let mut principal_roles = principal.roles().peekable(); - if principal_roles.peek().is_some() { - role_ids_stack.push(role_ids); - role_ids = principal_roles.collect::>().into_iter(); - } else { - // Cache role - self.inner - .cache - .permissions - .insert(role_id, Arc::new(role_permissions)); - } - } - } - } - } else if let Some(prev_role_ids) = role_ids_stack.pop() { - role_ids = prev_role_ids; - } else { - break; - } - } - - Ok(Arc::new(return_permissions)) - } -} - -impl PermissionsGroup { - pub fn union(&mut self, other: &PermissionsGroup) { - self.enabled.union(&other.enabled); - self.disabled.union(&other.disabled); - } - - pub fn finalize(mut self) -> Permissions { - self.enabled.difference(&self.disabled); - self.enabled - } - - pub fn finalize_as_ref(&self) -> Permissions { - let mut enabled = self.enabled.clone(); - enabled.difference(&self.disabled); - enabled - } -} diff --git a/crates/common/src/cache/directory.rs b/crates/common/src/cache/directory.rs new file mode 100644 index 00000000..db8ae156 --- /dev/null +++ b/crates/common/src/cache/directory.rs @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +use crate::{ + Server, + auth::{AccountCache, ApiKeyCache, DomainCache, EmailCache, RoleCache, TenantCache}, + config::smtp::auth::DkimSigner, +}; +use std::sync::Arc; + +impl Server { + pub async fn domain(&self, domain: &str) -> trc::Result>> { + todo!() + } + + pub async fn email(&self, address: &str) -> trc::Result> { + todo!() + } + + pub async fn account(&self, id: u32) -> trc::Result> { + todo!() + } + + pub async fn group(&self, id: u32) -> trc::Result> { + todo!() + } + + pub async fn role(&self, id: u32) -> trc::Result> { + todo!() + } + + pub async fn tenant(&self, id: u32) -> trc::Result>> { + todo!() + } + + pub async fn api_key(&self, id: u32) -> trc::Result>> { + todo!() + } + + pub async fn dkim_signers(&self, domain_id: u32) -> trc::Result>> { + todo!() + } +} diff --git a/crates/common/src/cache/mod.rs b/crates/common/src/cache/mod.rs new file mode 100644 index 00000000..c9878942 --- /dev/null +++ b/crates/common/src/cache/mod.rs @@ -0,0 +1,8 @@ +/* + * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL + */ + +pub mod directory; +pub mod reload; diff --git a/crates/common/src/manager/reload.rs b/crates/common/src/cache/reload.rs similarity index 95% rename from crates/common/src/manager/reload.rs rename to crates/common/src/cache/reload.rs index 2f58619d..dbf1c56f 100644 --- a/crates/common/src/manager/reload.rs +++ b/crates/common/src/cache/reload.rs @@ -11,7 +11,6 @@ use crate::{ }; use ahash::AHashMap; use arc_swap::ArcSwap; -use utils::config::Config; pub struct ReloadResult { pub config: Config, @@ -135,13 +134,3 @@ impl Server { })*/ } } - -impl From for ReloadResult { - fn from(config: Config) -> Self { - Self { - config, - new_core: None, - tracers: None, - } - } -} diff --git a/crates/common/src/config/inner.rs b/crates/common/src/config/inner.rs index 6a7b0101..4f381b90 100644 --- a/crates/common/src/config/inner.rs +++ b/crates/common/src/config/inner.rs @@ -6,8 +6,8 @@ use super::server::tls::build_self_signed_cert; use crate::{ - CacheSwap, Caches, Data, DavResource, DavResources, MailboxCache, MessageStoreCache, - MessageUidCache, TlsConnectors, + Caches, Data, DavResource, DavResources, MailboxCache, MessageStoreCache, MessageUidCache, + TlsConnectors, auth::AccessToken, config::{ mailstore::spamfilter::SpamClassifier, @@ -100,7 +100,7 @@ impl Caches { messages: Cache::new( cache.messages, (std::mem::size_of::() - + std::mem::size_of::>() + + std::mem::size_of::>() + (1024 * std::mem::size_of::()) + (15 * (std::mem::size_of::() + 60))) as u64, ), @@ -156,10 +156,10 @@ impl Caches { '_, T, CacheWithTtl, Txt>, - CacheWithTtl, Arc>>, - CacheWithTtl, Arc>>, - CacheWithTtl, Arc>>, - CacheWithTtl]>>>, + CacheWithTtl, Arc<[MX]>>, + CacheWithTtl, Arc<[Ipv4Addr]>>, + CacheWithTtl, Arc<[Ipv6Addr]>>, + CacheWithTtl]>>, > { Parameters { params, diff --git a/crates/common/src/config/smtp/auth.rs b/crates/common/src/config/smtp/auth.rs index a39eefef..17770004 100644 --- a/crates/common/src/config/smtp/auth.rs +++ b/crates/common/src/config/smtp/auth.rs @@ -23,6 +23,7 @@ use registry::{ }; use rustls_pki_types::{PrivateKeyDer, PrivatePkcs1KeyDer, PrivatePkcs8KeyDer, pem::PemObject}; use store::registry::bootstrap::Bootstrap; +use utils::cache::CacheItemWeight; #[derive(Clone)] pub struct MailAuthConfig { @@ -418,3 +419,9 @@ impl VerifyStrategy { matches!(self, VerifyStrategy::Strict) } } + +impl CacheItemWeight for DkimSigner { + fn weight(&self) -> u64 { + std::mem::size_of::() as u64 + } +} diff --git a/crates/common/src/config/storage.rs b/crates/common/src/config/storage.rs index fb91345d..13a28f0f 100644 --- a/crates/common/src/config/storage.rs +++ b/crates/common/src/config/storage.rs @@ -4,19 +4,21 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use crate::auth::IdMap; use coordinator::Coordinator; use directory::Directory; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use store::{BlobStore, InMemoryStore, RegistryStore, SearchStore, Store}; +pub type IdMap = HashMap, nohash_hasher::BuildNoHashHasher>; + #[derive(Clone)] pub struct Storage { pub registry: RegistryStore, pub data: Store, pub blob: BlobStore, pub fts: SearchStore, - pub lookup: InMemoryStore, - pub pubsub: Coordinator, - pub directories: IdMap>, + pub memory: InMemoryStore, + pub coordinator: Coordinator, + pub directory: Option>, + pub directories: IdMap, } diff --git a/crates/common/src/core.rs b/crates/common/src/core.rs index 34a8646b..02fc002b 100644 --- a/crates/common/src/core.rs +++ b/crates/common/src/core.rs @@ -20,6 +20,7 @@ use crate::{ ipc::{BroadcastEvent, PushEvent, PushNotification}, manager::SPAM_CLASSIFIER_KEY, }; +use directory::Directory; use mail_auth::IpLookupStrategy; use sieve::Sieve; use std::{ @@ -73,31 +74,16 @@ impl Server { &self.core.storage.lookup } - #[inline(always)] - pub fn directory(&self) -> &Directory { - &self.core.storage.directory - } - pub fn get_directory(&self, name: &str) -> Option<&Arc> { self.core.storage.directories.get(name) } - pub fn get_directory_or_default(&self, name: &str, session_id: u64) -> &Arc { - self.core.storage.directories.get(name).unwrap_or_else(|| { - if !name.is_empty() { - trc::event!( - Eval(trc::EvalEvent::DirectoryNotFound), - Id = name.to_string(), - SpanId = session_id, - ); - } - - &self.core.storage.directory - }) + pub fn get_default_directory(&self) -> Option<&Arc> { + self.core.storage.directory.as_ref() } - pub fn get_in_memory_store(&self, name: &str) -> Option<&InMemoryStore> { - self.core.storage.lookups.get(name) + pub fn get_lookup_store(&self, name: &str) -> Option { + self.inner.data.lookup_stores.load().get(name).cloned() } pub fn get_dkim_signer(&self, name: &str, session_id: u64) -> Option> { @@ -334,7 +320,7 @@ impl Server { access_token: &AccessToken, account_id: u32, ) -> trc::Result { - Ok(if access_token.primary_id == account_id { + Ok(if access_token.account_id == account_id { ResourceToken { account_id, quota: access_token.quota, diff --git a/crates/common/src/dns.rs b/crates/common/src/dns.rs index 6a391feb..1e81ace0 100644 --- a/crates/common/src/dns.rs +++ b/crates/common/src/dns.rs @@ -4,11 +4,9 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::net::IpAddr; - -use mail_auth::{Error, IpLookupStrategy}; - use crate::Server; +use mail_auth::{Error, IpLookupStrategy}; +use std::net::IpAddr; impl Server { pub async fn dns_exists_mx(&self, entry: &str) -> trc::Result { diff --git a/crates/common/src/enterprise/config.rs b/crates/common/src/enterprise/config.rs index 6547bca2..b5c49859 100644 --- a/crates/common/src/enterprise/config.rs +++ b/crates/common/src/enterprise/config.rs @@ -12,7 +12,7 @@ use super::{ AlertContent, AlertContentToken, AlertMethod, Enterprise, MetricAlert, MetricStore, SpamFilterLlmConfig, TraceStore, Undelete, license::LicenseKey, llm::AiApiConfig, }; -use crate::enterprise::llm::ApiType; +use crate::{enterprise::llm::ApiType, expr::if_block::BootstrapExprExt}; use ahash::AHashMap; use registry::{ schema::{ diff --git a/crates/common/src/enterprise/mod.rs b/crates/common/src/enterprise/mod.rs index 6514c3a6..1dca3705 100644 --- a/crates/common/src/enterprise/mod.rs +++ b/crates/common/src/enterprise/mod.rs @@ -26,7 +26,7 @@ use registry::types::id::Id; use std::{sync::Arc, time::Duration}; use store::Store; use trc::{AddContext, MetricType}; -use utils::{HttpLimitResponse, config::cron::SimpleCron, template::Template}; +use utils::{HttpLimitResponse, cron::SimpleCron, template::Template}; #[derive(Clone)] pub struct Enterprise { diff --git a/crates/common/src/expr/functions/asynch.rs b/crates/common/src/expr/functions/asynch.rs index 51e029ae..c1e4fc3e 100644 --- a/crates/common/src/expr/functions/asynch.rs +++ b/crates/common/src/expr/functions/asynch.rs @@ -4,17 +4,14 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::{cmp::Ordering, net::IpAddr, vec::IntoIter}; - +use super::*; +use crate::{Server, expr::StringCow}; use compact_str::{CompactString, ToCompactString}; use mail_auth::IpLookupStrategy; +use std::{cmp::Ordering, net::IpAddr, vec::IntoIter}; use store::{Deserialize, Rows, Value, dispatch::lookup::KeyValue}; use trc::AddContext; -use crate::{Server, expr::StringCow}; - -use super::*; - impl Server { pub(crate) async fn eval_fnc<'x>( &self, diff --git a/crates/common/src/expr/tokenizer.rs b/crates/common/src/expr/tokenizer.rs index 45ac1fb0..f13a5b58 100644 --- a/crates/common/src/expr/tokenizer.rs +++ b/crates/common/src/expr/tokenizer.rs @@ -116,7 +116,7 @@ impl<'x> Tokenizer<'x> { b"system" => { let stop_ch = self.find_char(b"\"'")?; let var = match self.parse_string(stop_ch)?.as_str() { - "default_domain" => SystemVariable::Domain, + "domain" => SystemVariable::Domain, "hostname" => SystemVariable::Hostname, "node_id" => SystemVariable::NodeId, other => { diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index e3f23ac5..46fbfe09 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -7,18 +7,25 @@ #![warn(clippy::large_futures)] use crate::{ - auth::DirectoryEntries, - config::mailstore::{ - email::EmailConfig, - imap::ImapConfig, - scripts::Scripting, - spamfilter::{IpResolver, SpamClassifier, SpamFilterConfig}, + auth::{ + AccountCache, ApiKeyCache, DomainCache, EmailCache, MailingListCache, RoleCache, + TenantCache, + }, + config::{ + mailstore::{ + email::EmailConfig, + imap::ImapConfig, + scripts::Scripting, + spamfilter::{IpResolver, SpamClassifier, SpamFilterConfig}, + }, + smtp::auth::DkimSigner, }, ipc::TrainTaskController, listener::blocked::BlockedIps, }; use ahash::{AHashMap, AHashSet}; use arc_swap::ArcSwap; +use arcstr::ArcStr; use auth::{AccessToken, oauth::config::OAuthConfig}; use calcard::common::timezone::Tz; use config::{ @@ -57,8 +64,8 @@ use utils::{ snowflake::SnowflakeIdGenerator, }; -pub mod addresses; pub mod auth; +pub mod cache; pub mod config; pub mod core; pub mod dns; @@ -138,7 +145,6 @@ pub struct Server { pub struct Inner { pub shared_core: ArcSwap, pub data: Data, - pub directory: DirectoryEntries, pub cache: Caches, pub ipc: Ipc, } @@ -169,25 +175,37 @@ pub struct Caches { pub access_tokens: Cache>, pub http_auth: Cache, HttpAuthCache>, - pub messages: Cache>, - pub files: Cache>, - pub contacts: Cache>, - pub events: Cache>, - pub scheduling: Cache>, + pub messages: Cache>, + pub files: Cache>, + pub contacts: Cache>, + pub events: Cache>, + pub scheduling: Cache>, + + pub emails: Cache, + pub emails_temporary: CacheWithTtl, + pub emails_negative: CacheWithTtl, + pub domains: Cache>, + pub domains_negative: CacheWithTtl, + + pub accounts: Cache>, + pub groups: Cache>, + pub roles: Cache>, + pub tenants: Cache>, + pub lists: Cache>, + pub api_keys: Cache>, + + pub dkim_signers: Cache, Arc<[DkimSigner]>>, pub dns_txt: CacheWithTtl, Txt>, - pub dns_mx: CacheWithTtl, Arc>>, - pub dns_ptr: CacheWithTtl]>>>, - pub dns_ipv4: CacheWithTtl, Arc>>, - pub dns_ipv6: CacheWithTtl, Arc>>, + pub dns_mx: CacheWithTtl, Arc<[MX]>>, + pub dns_ptr: CacheWithTtl]>>, + pub dns_ipv4: CacheWithTtl, Arc<[Ipv4Addr]>>, + pub dns_ipv6: CacheWithTtl, Arc<[Ipv6Addr]>>, pub dns_tlsa: CacheWithTtl, Arc>, pub dns_mta_sts: CacheWithTtl, Arc>, pub dns_rbl: CacheWithTtl, Option>>, } -#[derive(Debug, Clone)] -pub struct CacheSwap(pub Arc>); - #[derive(Debug, Clone)] pub struct MessageStoreCache { pub emails: Arc, @@ -367,12 +385,6 @@ pub struct Core { // SPDX-SnippetEnd } -impl CacheItemWeight for CacheSwap { - fn weight(&self) -> u64 { - std::mem::size_of::>() as u64 + self.0.load().weight() - } -} - impl CacheItemWeight for MessageStoreCache { fn weight(&self) -> u64 { self.size @@ -837,20 +849,6 @@ impl DavName { } } -impl CacheSwap { - pub fn new(value: Arc) -> Self { - Self(Arc::new(ArcSwap::new(value))) - } - - pub fn load_full(&self) -> Arc { - self.0.load_full() - } - - pub fn update(&self, value: Arc) { - self.0.store(value); - } -} - impl MailboxCache { pub fn parent_id(&self) -> Option { if self.parent_id != u32::MAX { diff --git a/crates/common/src/manager/boot.rs b/crates/common/src/manager/boot.rs index 6baf7f8e..030fa808 100644 --- a/crates/common/src/manager/boot.rs +++ b/crates/common/src/manager/boot.rs @@ -23,11 +23,7 @@ use std::{ }; use store::rand::{Rng, distr::Alphanumeric, rng}; use tokio::sync::{Notify, mpsc}; -use utils::{ - UnwrapFailure, - config::{Config, ConfigKey}, - failed, -}; +use utils::{UnwrapFailure, failed}; pub struct BootManager { pub config: Config, diff --git a/crates/common/src/manager/mod.rs b/crates/common/src/manager/mod.rs index 42007262..c8eb503a 100644 --- a/crates/common/src/manager/mod.rs +++ b/crates/common/src/manager/mod.rs @@ -12,7 +12,6 @@ use utils::HttpLimitResponse; pub mod backup; pub mod boot; pub mod console; -pub mod reload; pub mod restore; pub mod webadmin; diff --git a/crates/common/src/scripts/plugins/llm_prompt.rs b/crates/common/src/scripts/plugins/llm_prompt.rs index 21a0ec0d..6a6bb776 100644 --- a/crates/common/src/scripts/plugins/llm_prompt.rs +++ b/crates/common/src/scripts/plugins/llm_prompt.rs @@ -34,13 +34,17 @@ pub async fn exec(ctx: PluginContext<'_>) -> trc::Result { if let Some(ai_api) = ctx.server.core.enterprise.as_ref().and_then(|e| { if ctx.access_token.is_none_or(|token| { + use registry::schema::enums::Permission; + if token.has_permission(Permission::AiModelInteract) { true } else { + use registry::types::EnumType; + trc::event!( Security(SecurityEvent::Unauthorized), - AccountId = token.primary_id(), - Details = Permission::AiModelInteract.name(), + AccountId = token.account_id(), + Details = Permission::AiModelInteract.as_str(), SpanId = ctx.session_id, ); false diff --git a/crates/common/src/scripts/plugins/lookup.rs b/crates/common/src/scripts/plugins/lookup.rs index 9e73004d..d045c47f 100644 --- a/crates/common/src/scripts/plugins/lookup.rs +++ b/crates/common/src/scripts/plugins/lookup.rs @@ -22,13 +22,13 @@ pub fn register_set(plugin_id: u32, fnc_map: &mut FunctionMap) { } pub fn register_local_domain(plugin_id: u32, fnc_map: &mut FunctionMap) { - fnc_map.set_external_function("is_local_domain", plugin_id, 2); + fnc_map.set_external_function("is_local_domain", plugin_id, 1); } pub async fn exec(ctx: PluginContext<'_>) -> trc::Result { let store = match &ctx.arguments[0] { - Variable::String(v) if !v.is_empty() => ctx.server.core.storage.lookups.get(v.as_ref()), - _ => Some(&ctx.server.core.storage.lookup), + Variable::String(v) if !v.is_empty() => ctx.server.get_lookup_store(v.as_str()), + _ => Some(ctx.server.core.storage.memory.clone()), } .ok_or_else(|| { trc::SieveEvent::RuntimeError @@ -53,8 +53,8 @@ pub async fn exec(ctx: PluginContext<'_>) -> trc::Result { pub async fn exec_get(ctx: PluginContext<'_>) -> trc::Result { match &ctx.arguments[0] { - Variable::String(v) if !v.is_empty() => ctx.server.core.storage.lookups.get(v.as_ref()), - _ => Some(&ctx.server.core.storage.lookup), + Variable::String(v) if !v.is_empty() => ctx.server.get_lookup_store(v.as_str()), + _ => Some(ctx.server.core.storage.memory.clone()), } .ok_or_else(|| { trc::SieveEvent::RuntimeError @@ -74,8 +74,8 @@ pub async fn exec_set(ctx: PluginContext<'_>) -> trc::Result { }; match &ctx.arguments[0] { - Variable::String(v) if !v.is_empty() => ctx.server.core.storage.lookups.get(v.as_ref()), - _ => Some(&ctx.server.core.storage.lookup), + Variable::String(v) if !v.is_empty() => ctx.server.get_lookup_store(v.as_str()), + _ => Some(ctx.server.core.storage.memory.clone()), } .ok_or_else(|| { trc::SieveEvent::RuntimeError @@ -99,7 +99,7 @@ pub async fn exec_set(ctx: PluginContext<'_>) -> trc::Result { } pub async fn exec_local_domain(ctx: PluginContext<'_>) -> trc::Result { - let domain = ctx.arguments[1].to_string(); + let domain = ctx.arguments[0].to_string(); if !domain.is_empty() { return match &ctx.arguments[0] { diff --git a/crates/common/src/scripts/plugins/query.rs b/crates/common/src/scripts/plugins/query.rs index 3b9f7b61..25ff8c8d 100644 --- a/crates/common/src/scripts/plugins/query.rs +++ b/crates/common/src/scripts/plugins/query.rs @@ -4,14 +4,12 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ -use std::cmp::Ordering; - +use super::PluginContext; use crate::scripts::{into_sieve_value, to_store_value}; use sieve::{FunctionMap, runtime::Variable}; +use std::cmp::Ordering; use store::{Rows, Value}; -use super::PluginContext; - pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap) { fnc_map.set_external_function("query", plugin_id, 3); } @@ -19,8 +17,11 @@ pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap) { pub async fn exec(ctx: PluginContext<'_>) -> trc::Result { // Obtain store name let store = match &ctx.arguments[0] { - Variable::String(v) if !v.is_empty() => ctx.server.core.storage.stores.get(v.as_ref()), - _ => Some(&ctx.server.core.storage.data), + Variable::String(v) if !v.is_empty() => ctx + .server + .get_lookup_store(v.as_str()) + .and_then(|v| v.as_store().cloned()), + _ => Some(ctx.server.core.storage.data.clone()), } .ok_or_else(|| { trc::SieveEvent::RuntimeError diff --git a/crates/common/src/sharing/acl.rs b/crates/common/src/sharing/acl.rs index 3b921abe..84c92911 100644 --- a/crates/common/src/sharing/acl.rs +++ b/crates/common/src/sharing/acl.rs @@ -9,7 +9,7 @@ use types::acl::{AclGrant, ArchivedAclGrant}; impl Server { pub async fn refresh_acls(&self, acl_changes: &[AclGrant], current: Option<&[AclGrant]>) { - let mut changed_principals = ChangedPrincipals::new(); + let mut changed_principals = Vec::new(); if let Some(acl_current) = current { for current_item in acl_current { let mut invalidate = true; @@ -20,11 +20,7 @@ impl Server { } } if invalidate { - changed_principals.add_change( - current_item.account_id, - Type::Individual, - PrincipalField::EnabledPermissions, - ); + changed_principals.push(current_item.account_id); } } @@ -37,20 +33,12 @@ impl Server { } } if invalidate { - changed_principals.add_change( - change_item.account_id, - Type::Individual, - PrincipalField::EnabledPermissions, - ); + changed_principals.push(change_item.account_id); } } } else { for value in acl_changes { - changed_principals.add_change( - value.account_id, - Type::Individual, - PrincipalField::EnabledPermissions, - ); + changed_principals.push(value.account_id); } } @@ -62,7 +50,7 @@ impl Server { acl_changes: &[AclGrant], acl_current: &[ArchivedAclGrant], ) { - let mut changed_principals = ChangedPrincipals::new(); + let mut changed_principals = Vec::new(); for current_item in acl_current.iter() { let mut invalidate = true; for change_item in acl_changes { @@ -72,11 +60,7 @@ impl Server { } } if invalidate { - changed_principals.add_change( - current_item.account_id.to_native(), - Type::Individual, - PrincipalField::EnabledPermissions, - ); + changed_principals.push(current_item.account_id.to_native()); } } @@ -89,11 +73,7 @@ impl Server { } } if invalidate { - changed_principals.add_change( - change_item.account_id, - Type::Individual, - PrincipalField::EnabledPermissions, - ); + changed_principals.push(change_item.account_id); } } diff --git a/crates/common/src/storage/index.rs b/crates/common/src/storage/index.rs index 5020e73b..8ad1f473 100644 --- a/crates/common/src/storage/index.rs +++ b/crates/common/src/storage/index.rs @@ -301,7 +301,7 @@ impl ObjectIndexBuilder Self { self.tenant_id = access_token.tenant.as_ref().map(|t| t.id); - self.changed_by = access_token.primary_id(); + self.changed_by = access_token.account_id(); self } diff --git a/crates/dav/src/common/lock.rs b/crates/dav/src/common/lock.rs index 0302cb78..42cb2e70 100644 --- a/crates/dav/src/common/lock.rs +++ b/crates/dav/src/common/lock.rs @@ -202,7 +202,7 @@ impl LockRequestHandler for Server { locks .0 .iter() - .filter(|lock| lock.owner == access_token.primary_id) + .filter(|lock| lock.owner == access_token.account_id) }) .count() >= self.core.groupware.max_locks_per_user @@ -270,7 +270,7 @@ impl LockRequestHandler for Server { } lock_item.lock_id = store::rand::random::() ^ expires; - lock_item.owner = access_token.primary_id; + lock_item.owner = access_token.account_id; lock_item.depth_infinity = matches!(headers.depth, Depth::Infinity); lock_item.owner_dav = lock_info.owner; lock_item.exclusive = matches!(lock_info.lock_scope, LockScope::Exclusive); diff --git a/crates/dav/src/common/propfind.rs b/crates/dav/src/common/propfind.rs index 8d886c09..7d1db777 100644 --- a/crates/dav/src/common/propfind.rs +++ b/crates/dav/src/common/propfind.rs @@ -621,7 +621,7 @@ impl PropFindRequestHandler for Server { property.clone(), self.expand_principal( access_token, - access_token.primary_id(), + access_token.account_id(), &query.propfind, ) .await? @@ -1420,14 +1420,14 @@ async fn get( // This is invalid but it's the only workaround for clients which do not support multiple home-sets if server.core.groupware.assisted_discovery && !is_sync - && account_id == access_token.primary_id() + && account_id == access_token.account_id() && matches!( sync_collection, SyncCollection::Calendar | SyncCollection::AddressBook ) { for shared_account_id in access_token.all_ids_by_collection(collection_container) { - if shared_account_id == access_token.primary_id() { + if shared_account_id == access_token.account_id() { continue; } let shared_resources = data @@ -1745,7 +1745,7 @@ async fn add_base_collection_response( server, access_token, &access_token.name, - access_token.primary_id, + access_token.account_id, true, ) .await @@ -1759,7 +1759,7 @@ async fn add_base_collection_response( server, access_token, &access_token.name, - access_token.primary_id, + access_token.account_id, false, ) .await diff --git a/crates/dav/src/common/uri.rs b/crates/dav/src/common/uri.rs index 4ef0eba9..b8d9d6eb 100644 --- a/crates/dav/src/common/uri.rs +++ b/crates/dav/src/common/uri.rs @@ -93,7 +93,7 @@ impl DavUriResource for Server { } else { let account = decode_path_element(account); if access_token.name == account { - access_token.primary_id + access_token.account_id } else { self.store() .get_principal_id(&account) diff --git a/crates/dav/src/principal/propfind.rs b/crates/dav/src/principal/propfind.rs index 2c97fcaa..49bcac6e 100644 --- a/crates/dav/src/principal/propfind.rs +++ b/crates/dav/src/principal/propfind.rs @@ -106,7 +106,7 @@ impl PrincipalPropFind for Server { let mut fields = Vec::with_capacity(properties.len()); let mut fields_not_found = Vec::new(); - let (name, description, emails, typ) = if access_token.primary_id() == account_id { + let (name, description, emails, typ) = if access_token.account_id() == account_id { ( Cow::Borrowed(access_token.name.as_str()), access_token @@ -411,7 +411,7 @@ impl PrincipalPropFind for Server { } async fn owner_href(&self, access_token: &AccessToken, account_id: u32) -> trc::Result { - if access_token.primary_id() == account_id { + if access_token.account_id() == account_id { Ok(access_token.current_user_principal()) } else { let name = self @@ -449,9 +449,9 @@ pub(crate) async fn build_home_set( percent_encoding::utf8_percent_encode(name, RFC_3986), ))); - if !server.core.groupware.assisted_discovery && account_id == access_token.primary_id() { + if !server.core.groupware.assisted_discovery && account_id == access_token.account_id() { for account_id in access_token.all_ids_by_collection(collection) { - if account_id != access_token.primary_id() { + if account_id != access_token.account_id() { let other_name = server .store() .get_principal_name(account_id) diff --git a/crates/directory/src/core/config.rs b/crates/directory/src/core/config.rs index c3141457..e2a076bd 100644 --- a/crates/directory/src/core/config.rs +++ b/crates/directory/src/core/config.rs @@ -11,7 +11,7 @@ use crate::{ use ahash::AHashMap; use registry::schema::{ prelude::Object, - structs::{self, Authentication}, + structs::{self, DefaultDirectory}, }; use std::sync::Arc; use store::registry::bootstrap::Bootstrap; @@ -40,21 +40,23 @@ impl Directories { } } - let mut default_directory = None; - let auth = bp.setting_infallible::().await; - if let Some(id) = auth.directory_id { - if let Some(directory) = directories.get(&id) { - default_directory = Some(directory.clone()); - } else { - bp.build_error( - Object::Authentication.singleton(), - format!("Default directory with id {} not found", id), - ); - } - } + let default_directory = match bp.setting_infallible::().await { + DefaultDirectory::Internal => Ok(None), + DefaultDirectory::Ldap(directory) => LdapDirectory::open(directory).map(Some), + DefaultDirectory::Sql(directory) => SqlDirectory::open(directory, &bp.data_store) + .await + .map(Some), + DefaultDirectory::Oidc(directory) => OpenIdDirectory::open(directory).map(Some), + }; Directories { - default_directory, + default_directory: match default_directory { + Ok(default_directory) => default_directory.map(Arc::new), + Err(err) => { + bp.build_error(Object::DefaultDirectory.singleton(), err); + None + } + }, directories, } } diff --git a/crates/email/src/message/ingest.rs b/crates/email/src/message/ingest.rs index 2faaea32..a0faac41 100644 --- a/crates/email/src/message/ingest.rs +++ b/crates/email/src/message/ingest.rs @@ -131,7 +131,7 @@ impl EmailIngest for Server { async fn email_ingest(&self, mut params: IngestEmail<'_>) -> trc::Result { // Check quota let start_time = Instant::now(); - let account_id = params.access_token.primary_id; + let account_id = params.access_token.account_id; let tenant_id = params.access_token.tenant.map(|t| t.id); let mut raw_message_len = params.raw_message.len() as u64; let resource_token = params.access_token.as_resource_token(); diff --git a/crates/email/src/sieve/ingest.rs b/crates/email/src/sieve/ingest.rs index f3854dcb..2fed5224 100644 --- a/crates/email/src/sieve/ingest.rs +++ b/crates/email/src/sieve/ingest.rs @@ -108,7 +108,7 @@ impl SieveScriptIngest for Server { }; // Obtain mailboxIds - let account_id = access_token.primary_id; + let account_id = access_token.account_id; let mut cache = self .get_cached_messages(account_id) .await diff --git a/crates/groupware/src/calendar/itip.rs b/crates/groupware/src/calendar/itip.rs index 10ec2373..8cada62e 100644 --- a/crates/groupware/src/calendar/itip.rs +++ b/crates/groupware/src/calendar/itip.rs @@ -149,7 +149,7 @@ impl ItipIngest for Server { }; // Find event by UID - let account_id = access_token.primary_id; + let account_id = access_token.account_id; let document_id = self .document_ids_matching( account_id, diff --git a/crates/groupware/src/calendar/mod.rs b/crates/groupware/src/calendar/mod.rs index f38787dd..bf5bdc2f 100644 --- a/crates/groupware/src/calendar/mod.rs +++ b/crates/groupware/src/calendar/mod.rs @@ -216,7 +216,7 @@ impl Calendar { if self.preferences.len() == 1 { &self.preferences[0] } else { - let account_id = access_token.primary_id(); + let account_id = access_token.account_id(); self.preferences .iter() .find(|p| p.account_id == account_id) @@ -226,7 +226,7 @@ impl Calendar { } pub fn preferences_mut(&mut self, access_token: &AccessToken) -> &mut CalendarPreferences { - let account_id = access_token.primary_id(); + let account_id = access_token.account_id(); let idx = if let Some(idx) = self .preferences .iter() @@ -260,7 +260,7 @@ impl ArchivedCalendar { if self.preferences.len() == 1 { &self.preferences[0] } else { - let account_id = access_token.primary_id(); + let account_id = access_token.account_id(); self.preferences .iter() .find(|p| p.account_id == account_id) @@ -274,11 +274,11 @@ impl CalendarEvent { pub fn preferences(&self, access_token: &AccessToken) -> Option<&EventPreferences> { self.preferences .iter() - .find(|p| p.account_id == access_token.primary_id()) + .find(|p| p.account_id == access_token.account_id()) } pub fn preferences_mut(&mut self, access_token: &AccessToken) -> &mut EventPreferences { - let account_id = access_token.primary_id(); + let account_id = access_token.account_id(); let idx = if let Some(idx) = self .preferences .iter() @@ -334,7 +334,7 @@ impl ArchivedCalendarEvent { pub fn preferences(&self, access_token: &AccessToken) -> Option<&ArchivedEventPreferences> { self.preferences .iter() - .find(|p| p.account_id == access_token.primary_id()) + .find(|p| p.account_id == access_token.account_id()) } } diff --git a/crates/groupware/src/calendar/storage.rs b/crates/groupware/src/calendar/storage.rs index 962d20a6..8ebd956b 100644 --- a/crates/groupware/src/calendar/storage.rs +++ b/crates/groupware/src/calendar/storage.rs @@ -576,7 +576,7 @@ impl ArchivedCalendarEvent { if let Some(calendar_) = server .store() .get_value::>(ValueKey::archive( - access_token.primary_id, + access_token.account_id, Collection::Calendar, event_name.parent_id.to_native(), )) diff --git a/crates/groupware/src/contact/mod.rs b/crates/groupware/src/contact/mod.rs index ae2e383c..32f4e08f 100644 --- a/crates/groupware/src/contact/mod.rs +++ b/crates/groupware/src/contact/mod.rs @@ -54,7 +54,7 @@ impl AddressBook { if self.preferences.len() == 1 { &self.preferences[0] } else { - let account_id = access_token.primary_id(); + let account_id = access_token.account_id(); self.preferences .iter() .find(|p| p.account_id == account_id) @@ -64,7 +64,7 @@ impl AddressBook { } pub fn preferences_mut(&mut self, access_token: &AccessToken) -> &mut AddressBookPreferences { - let account_id = access_token.primary_id(); + let account_id = access_token.account_id(); let idx = if let Some(idx) = self .preferences .iter() @@ -87,7 +87,7 @@ impl ArchivedAddressBook { if self.preferences.len() == 1 { &self.preferences[0] } else { - let account_id = access_token.primary_id(); + let account_id = access_token.account_id(); self.preferences .iter() .find(|p| p.account_id == account_id) diff --git a/crates/http/Cargo.toml b/crates/http/Cargo.toml index 7afacb70..a517a8f6 100644 --- a/crates/http/Cargo.toml +++ b/crates/http/Cargo.toml @@ -22,7 +22,7 @@ services = { path = "../services" } smtp-proto = { version = "0.2" } mail-parser = { version = "0.11", features = ["full_encoding", "rkyv"] } mail-builder = { version = "0.4" } -mail-auth = { version = "0.7.1", features = ["generate"] } +mail-auth = { path = "/Users/me/code/mail-auth", features = ["generate"] } mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } tokio = { version = "1.47", features = ["rt"] } hyper = { version = "1.0.1", features = ["server", "http1", "http2"] } diff --git a/crates/http/src/auth/authenticate.rs b/crates/http/src/auth/authenticate.rs index e14f61e3..13976360 100644 --- a/crates/http/src/auth/authenticate.rs +++ b/crates/http/src/auth/authenticate.rs @@ -98,7 +98,7 @@ impl Authenticator for Server { self.inner.cache.http_auth.insert( token.to_string(), HttpAuthCache { - account_id: access_token.primary_id(), + account_id: access_token.account_id(), revision: access_token.revision, expires: Instant::now() + Duration::from_secs(self.core.oauth.oauth_expiry_token), diff --git a/crates/http/src/auth/oauth/auth.rs b/crates/http/src/auth/oauth/auth.rs index 5addd0fc..9c9502b4 100644 --- a/crates/http/src/auth/oauth/auth.rs +++ b/crates/http/src/auth/oauth/auth.rs @@ -109,7 +109,7 @@ impl OAuthApiHandler for Server { // Serialize OAuth code let value = Archiver::new(OAuthCode { status: OAuthStatus::Authorized, - account_id: access_token.primary_id(), + account_id: access_token.account_id(), client_id, nonce, params: redirect_uri.unwrap_or_default(), @@ -167,7 +167,7 @@ impl OAuthApiHandler for Server { if oauth.status == OAuthStatus::Pending { let new_oauth_code = OAuthCode { status: OAuthStatus::Authorized, - account_id: access_token.primary_id(), + account_id: access_token.account_id(), client_id: oauth.client_id.to_string(), nonce: oauth.nonce.as_ref().map(|s| s.to_string()), params: Default::default(), diff --git a/crates/http/src/auth/oauth/openid.rs b/crates/http/src/auth/oauth/openid.rs index 46d7e677..271f4169 100644 --- a/crates/http/src/auth/oauth/openid.rs +++ b/crates/http/src/auth/oauth/openid.rs @@ -50,7 +50,7 @@ impl OpenIdHandler for Server { access_token: &AccessToken, ) -> trc::Result { Ok(JsonResponse::new(Userinfo { - sub: Some(access_token.primary_id.to_string()), + sub: Some(access_token.account_id.to_string()), name: access_token.description.clone(), preferred_username: Some(access_token.name.clone()), email: access_token.emails.first().cloned(), diff --git a/crates/http/src/management/crypto.rs b/crates/http/src/management/crypto.rs index 0c1a672d..3b4ee22d 100644 --- a/crates/http/src/management/crypto.rs +++ b/crates/http/src/management/crypto.rs @@ -40,7 +40,7 @@ impl CryptoHandler for Server { let ec = if let Some(params_) = self .store() .get_value::>(ValueKey::property( - access_token.primary_id(), + access_token.account_id(), Collection::Principal, 0, PrincipalField::EncryptionKeys, @@ -104,7 +104,7 @@ impl CryptoHandler for Server { // Disable encryption at rest let mut batch = BatchBuilder::new(); batch - .with_account_id(access_token.primary_id()) + .with_account_id(access_token.account_id()) .with_collection(Collection::Principal) .with_document(0) .clear(PrincipalField::EncryptionKeys); @@ -159,7 +159,7 @@ impl CryptoHandler for Server { // Save encryption params let mut batch = BatchBuilder::new(); batch - .with_account_id(access_token.primary_id()) + .with_account_id(access_token.account_id()) .with_collection(Collection::Principal) .with_document(0) .set(PrincipalField::EncryptionKeys, params); diff --git a/crates/http/src/management/enterprise/telemetry.rs b/crates/http/src/management/enterprise/telemetry.rs index 98139395..5dd037fc 100644 --- a/crates/http/src/management/enterprise/telemetry.rs +++ b/crates/http/src/management/enterprise/telemetry.rs @@ -62,7 +62,7 @@ impl TelemetryApi for Server { access_token: &AccessToken, ) -> trc::Result { let params = UrlParams::new(req.uri().query()); - let account_id = access_token.primary_id(); + let account_id = access_token.account_id(); match ( path.get(1).copied().unwrap_or_default(), diff --git a/crates/http/src/management/principal.rs b/crates/http/src/management/principal.rs index 5986be70..fb559f13 100644 --- a/crates/http/src/management/principal.rs +++ b/crates/http/src/management/principal.rs @@ -690,10 +690,10 @@ impl PrincipalManager for Server { app_passwords: Vec::new(), }; - if access_token.primary_id() != u32::MAX { + if access_token.account_id() != u32::MAX { let principal = self .directory() - .query(QueryParams::id(access_token.primary_id()).with_return_member_of(false)) + .query(QueryParams::id(access_token.account_id()).with_return_member_of(false)) .await? .ok_or_else(|| trc::ManageEvent::NotFound.into_err())?; @@ -760,7 +760,7 @@ impl PrincipalManager for Server { } // Handle Fallback admin password changes - if access_token.primary_id() == u32::MAX { + if access_token.account_id() == u32::MAX { match requests.into_iter().next().unwrap() { AccountAuthRequest::SetPassword { password } => { self.core @@ -774,7 +774,7 @@ impl PrincipalManager for Server { // Increment revision self.invalidate_principal_caches(ChangedPrincipals::from_change( - access_token.primary_id(), + access_token.account_id(), Type::Individual, PrincipalField::Secrets, )) @@ -846,7 +846,7 @@ impl PrincipalManager for Server { .storage .data .update_principal( - UpdatePrincipal::by_id(access_token.primary_id()) + UpdatePrincipal::by_id(access_token.account_id()) .with_updates(actions) .with_tenant(access_token.tenant.map(|t| t.id)), ) diff --git a/crates/http/src/management/troubleshoot.rs b/crates/http/src/management/troubleshoot.rs index d5bea914..244dda61 100644 --- a/crates/http/src/management/troubleshoot.rs +++ b/crates/http/src/management/troubleshoot.rs @@ -64,7 +64,7 @@ impl TroubleshootApi for Server { body: Option>, ) -> trc::Result { let params = UrlParams::new(req.uri().query()); - let account_id = access_token.primary_id(); + let account_id = access_token.account_id(); match ( path.get(1).copied().unwrap_or_default(), diff --git a/crates/imap/src/core/mailbox.rs b/crates/imap/src/core/mailbox.rs index 7863d0b8..2ea56eca 100644 --- a/crates/imap/src/core/mailbox.rs +++ b/crates/imap/src/core/mailbox.rs @@ -39,7 +39,7 @@ impl SessionData { let mut session = SessionData { stream_tx: session.stream_tx.clone(), server: session.server.clone(), - account_id: access_token.primary_id(), + account_id: access_token.account_id(), session_id: session.session_id, mailboxes: Mutex::new(vec![]), state: access_token.state().into(), @@ -100,7 +100,7 @@ impl SessionData { return Ok(None); } - let shared_mailbox_ids = if access_token.is_primary_id(account_id) + let shared_mailbox_ids = if access_token.is_account_id(account_id) || access_token.member_of.contains(&account_id) { None @@ -168,7 +168,7 @@ impl SessionData { .items .iter() .any(|child| child.parent_id == mailbox.document_id), - is_subscribed: mailbox.subscribers.contains(&access_token.primary_id()), + is_subscribed: mailbox.subscribers.contains(&access_token.account_id()), special_use: match mailbox.role { SpecialUse::Trash => Some(Attribute::Trash), SpecialUse::Junk => Some(Attribute::Junk), @@ -236,7 +236,7 @@ impl SessionData { .copied() .collect::>(); for account in mailboxes.drain(..) { - if access_token.is_primary_id(account.account_id) + if access_token.is_account_id(account.account_id) || has_access_to.contains(&account.account_id) { new_accounts.push(account); diff --git a/crates/imap/src/core/mod.rs b/crates/imap/src/core/mod.rs index a6519c0b..5c99790c 100644 --- a/crates/imap/src/core/mod.rs +++ b/crates/imap/src/core/mod.rs @@ -83,7 +83,7 @@ pub struct SelectedMailbox { #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub struct AccountId { pub account_id: u32, - pub primary_id: u32, + pub account_id: u32, } #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] diff --git a/crates/jmap/Cargo.toml b/crates/jmap/Cargo.toml index 0a9498d0..0d2dd9de 100644 --- a/crates/jmap/Cargo.toml +++ b/crates/jmap/Cargo.toml @@ -23,7 +23,7 @@ smtp-proto = { version = "0.2" } mail-parser = { version = "0.11", features = ["full_encoding", "rkyv"] } mail-builder = { version = "0.4" } mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } -mail-auth = { version = "0.7.1", features = ["generate"] } +mail-auth = { path = "/Users/me/code/mail-auth", features = ["generate"] } sieve-rs = { version = "0.7", features = ["rkyv"] } jmap-tools = { version = "0.1", features = ["rkyv"] } serde = { version = "1.0", features = ["derive"]} diff --git a/crates/jmap/src/addressbook/get.rs b/crates/jmap/src/addressbook/get.rs index 8dbad3e0..a80f9e25 100644 --- a/crates/jmap/src/addressbook/get.rs +++ b/crates/jmap/src/addressbook/get.rs @@ -12,7 +12,11 @@ use jmap_proto::{ object::addressbook::{self, AddressBookProperty, AddressBookValue}, }; use jmap_tools::{Map, Value}; -use store::{ValueKey, roaring::RoaringBitmap, write::{AlignedBytes, Archive, ValueClass}}; +use store::{ + ValueKey, + roaring::RoaringBitmap, + write::{AlignedBytes, Archive, ValueClass}, +}; use trc::AddContext; use types::{ acl::{Acl, AclGrant}, @@ -154,7 +158,7 @@ impl AddressBookGet for Server { address_book .subscribers .iter() - .any(|account_id| *account_id == access_token.primary_id()), + .any(|account_id| *account_id == access_token.account_id()), ); } AddressBookProperty::ShareWith => { diff --git a/crates/jmap/src/addressbook/set.rs b/crates/jmap/src/addressbook/set.rs index cf868521..58268cce 100644 --- a/crates/jmap/src/addressbook/set.rs +++ b/crates/jmap/src/addressbook/set.rs @@ -404,7 +404,7 @@ fn update_address_book( address_book.preferences_mut(access_token).sort_order = value.cast_to_u64() as u32; } (AddressBookProperty::IsSubscribed, Value::Bool(subscribe)) => { - let account_id = access_token.primary_id(); + let account_id = access_token.account_id(); if subscribe { if !address_book.subscribers.contains(&account_id) { address_book.subscribers.push(account_id); diff --git a/crates/jmap/src/api/request.rs b/crates/jmap/src/api/request.rs index b14141c6..d7dd1fc1 100644 --- a/crates/jmap/src/api/request.rs +++ b/crates/jmap/src/api/request.rs @@ -186,7 +186,7 @@ impl RequestHandler for Server { trc::error!( error .span_id(session.session_id) - .ctx_unique(trc::Key::AccountId, access_token.primary_id()) + .ctx_unique(trc::Key::AccountId, access_token.account_id()) .caused_by(method_name) ); @@ -626,7 +626,7 @@ impl RequestHandler for Server { Jmap(JmapEvent::MethodCall), Id = method_name.as_str(), SpanId = session.session_id, - AccountId = access_token.primary_id(), + AccountId = access_token.account_id(), Elapsed = op_start.elapsed(), ); @@ -637,6 +637,6 @@ impl RequestHandler for Server { #[inline] pub(crate) fn set_account_id_if_missing(account_id: &mut Id, access_token: &AccessToken) { if !account_id.is_valid() { - *account_id = Id::from(access_token.primary_id()); + *account_id = Id::from(access_token.account_id()); } } diff --git a/crates/jmap/src/api/session.rs b/crates/jmap/src/api/session.rs index 1ad84b49..4249e0af 100644 --- a/crates/jmap/src/api/session.rs +++ b/crates/jmap/src/api/session.rs @@ -34,7 +34,7 @@ impl SessionHandler for Server { // Set primary account session.username = access_token.name.to_string(); - let account_id = Id::from(access_token.primary_id()); + let account_id = Id::from(access_token.account_id()); let mut account = Account { name: access_token.name.to_string(), is_personal: true, diff --git a/crates/jmap/src/calendar_event/get.rs b/crates/jmap/src/calendar_event/get.rs index 8fa719ab..4d626578 100644 --- a/crates/jmap/src/calendar_event/get.rs +++ b/crates/jmap/src/calendar_event/get.rs @@ -154,7 +154,7 @@ impl CalendarEventGet for Server { ) }; let return_is_origin = if return_is_origin { - if access_token.primary_id() == account_id { + if access_token.account_id() == account_id { OriginAddresses::Ref(access_token) } else { OriginAddresses::Owned(self.get_access_token(account_id).await?) diff --git a/crates/jmap/src/email/import.rs b/crates/jmap/src/email/import.rs index 8717b195..e6332e89 100644 --- a/crates/jmap/src/email/import.rs +++ b/crates/jmap/src/email/import.rs @@ -53,7 +53,7 @@ impl EmailImport for Server { }; // Obtain import access token - let import_access_token = if account_id != access_token.primary_id() { + let import_access_token = if account_id != access_token.account_id() { #[cfg(feature = "test_mode")] { std::sync::Arc::new(AccessToken::from_id(account_id)).into() diff --git a/crates/jmap/src/email/set.rs b/crates/jmap/src/email/set.rs index 032c55ee..496a71b6 100644 --- a/crates/jmap/src/email/set.rs +++ b/crates/jmap/src/email/set.rs @@ -97,7 +97,7 @@ impl EmailSet for Server { }; // Obtain import access token - let import_access_token = if account_id != access_token.primary_id() { + let import_access_token = if account_id != access_token.account_id() { #[cfg(feature = "test_mode")] { std::sync::Arc::new(AccessToken::from_id(account_id)).into() diff --git a/crates/jmap/src/mailbox/get.rs b/crates/jmap/src/mailbox/get.rs index 2b942c9c..e4f19b58 100644 --- a/crates/jmap/src/mailbox/get.rs +++ b/crates/jmap/src/mailbox/get.rs @@ -144,7 +144,7 @@ impl MailboxGet for Server { MailboxProperty::IsSubscribed => Value::Bool( cached_mailbox .subscribers - .contains(&access_token.primary_id()), + .contains(&access_token.account_id()), ), MailboxProperty::ShareWith => JmapRights::share_with::( account_id, diff --git a/crates/jmap/src/mailbox/query.rs b/crates/jmap/src/mailbox/query.rs index 08488e6e..b0c28acc 100644 --- a/crates/jmap/src/mailbox/query.rs +++ b/crates/jmap/src/mailbox/query.rs @@ -120,7 +120,7 @@ impl MailboxQuery for Server { .items .iter() .filter(|mailbox| { - mailbox.subscribers.contains(&access_token.primary_id) + mailbox.subscribers.contains(&access_token.account_id) == is_subscribed }) .map(|m| m.document_id) diff --git a/crates/jmap/src/mailbox/set.rs b/crates/jmap/src/mailbox/set.rs index d583ba85..052a00b0 100644 --- a/crates/jmap/src/mailbox/set.rs +++ b/crates/jmap/src/mailbox/set.rs @@ -374,7 +374,7 @@ impl MailboxSet for Server { changes.parent_id = 0; } (Key::Property(MailboxProperty::IsSubscribed), Value::Bool(subscribe)) => { - let account_id = ctx.access_token.primary_id(); + let account_id = ctx.access_token.account_id(); if subscribe { if !changes.subscribers.contains(&account_id) { changes.subscribers.push(account_id); diff --git a/crates/jmap/src/push/get.rs b/crates/jmap/src/push/get.rs index 2714ea81..fb8a3e8f 100644 --- a/crates/jmap/src/push/get.rs +++ b/crates/jmap/src/push/get.rs @@ -44,7 +44,7 @@ impl PushSubscriptionFetch for Server { PushSubscriptionProperty::Types, ]); - let account_id = access_token.primary_id(); + let account_id = access_token.account_id(); let mut response = GetResponse { account_id: request.account_id.into(), diff --git a/crates/jmap/src/push/set.rs b/crates/jmap/src/push/set.rs index 8b5578bf..10a75738 100644 --- a/crates/jmap/src/push/set.rs +++ b/crates/jmap/src/push/set.rs @@ -45,7 +45,7 @@ impl PushSubscriptionSet for Server { access_token: &AccessToken, ) -> trc::Result> { // Load existing push subscriptions - let account_id = access_token.primary_id(); + let account_id = access_token.account_id(); let subscriptions_archive = self .store() .get_value::>(ValueKey::property( diff --git a/crates/jmap/src/quota/get.rs b/crates/jmap/src/quota/get.rs index 338ba81f..e41c4936 100644 --- a/crates/jmap/src/quota/get.rs +++ b/crates/jmap/src/quota/get.rs @@ -60,7 +60,7 @@ impl QuotaGet for Server { not_found: vec![], }; - let access_token = if account_id == access_token.primary_id() { + let access_token = if account_id == access_token.account_id() { AccessTokenRef::Borrowed(access_token) } else { AccessTokenRef::Owned( diff --git a/crates/jmap/src/websocket/stream.rs b/crates/jmap/src/websocket/stream.rs index c448652e..7d027d8d 100644 --- a/crates/jmap/src/websocket/stream.rs +++ b/crates/jmap/src/websocket/stream.rs @@ -44,7 +44,7 @@ impl WebSocketHandler for Server { trc::event!( Jmap(JmapEvent::WebsocketStart), SpanId = session.session_id, - AccountId = access_token.primary_id(), + AccountId = access_token.account_id(), ); // Set timeouts diff --git a/crates/managesieve/src/core/client.rs b/crates/managesieve/src/core/client.rs index ce7a74ee..4fd73d87 100644 --- a/crates/managesieve/src/core/client.rs +++ b/crates/managesieve/src/core/client.rs @@ -188,7 +188,7 @@ impl Session { .lookup .is_rate_allowed( KV_RATE_LIMIT_IMAP, - &access_token.primary_id().to_be_bytes(), + &access_token.account_id().to_be_bytes(), rate, true, ) diff --git a/crates/managesieve/src/op/deletescript.rs b/crates/managesieve/src/op/deletescript.rs index 219d8e5a..facfff99 100644 --- a/crates/managesieve/src/op/deletescript.rs +++ b/crates/managesieve/src/op/deletescript.rs @@ -32,7 +32,7 @@ impl Session { })?; let access_token = self.state.access_token(); - let account_id = access_token.primary_id(); + let account_id = access_token.account_id(); let document_id = self.get_script_id(account_id, &name).await?; let mut batch = BatchBuilder::new(); diff --git a/crates/managesieve/src/op/getscript.rs b/crates/managesieve/src/op/getscript.rs index de37a18b..5d04abec 100644 --- a/crates/managesieve/src/op/getscript.rs +++ b/crates/managesieve/src/op/getscript.rs @@ -33,7 +33,7 @@ impl Session { .into_err() .details("Expected script name as a parameter.") })?; - let account_id = self.state.access_token().primary_id(); + let account_id = self.state.access_token().account_id(); let document_id = self.get_script_id(account_id, &name).await?; let sieve_ = self .server diff --git a/crates/managesieve/src/op/havespace.rs b/crates/managesieve/src/op/havespace.rs index 3442f026..e586ddb6 100644 --- a/crates/managesieve/src/op/havespace.rs +++ b/crates/managesieve/src/op/havespace.rs @@ -45,7 +45,7 @@ impl Session { // Validate name let access_token = self.state.access_token(); - let account_id = access_token.primary_id(); + let account_id = access_token.account_id(); self.validate_name(account_id, &name).await?; // Validate quota diff --git a/crates/managesieve/src/op/listscripts.rs b/crates/managesieve/src/op/listscripts.rs index 6b9cede9..ee0b76af 100644 --- a/crates/managesieve/src/op/listscripts.rs +++ b/crates/managesieve/src/op/listscripts.rs @@ -22,7 +22,7 @@ impl Session { self.assert_has_permission(Permission::SieveListScripts)?; let op_start = Instant::now(); - let account_id = self.state.access_token().primary_id(); + let account_id = self.state.access_token().account_id(); let document_ids = self .server .document_ids(account_id, Collection::SieveScript, SieveField::Name) diff --git a/crates/managesieve/src/op/putscript.rs b/crates/managesieve/src/op/putscript.rs index 8cd91591..893d5636 100644 --- a/crates/managesieve/src/op/putscript.rs +++ b/crates/managesieve/src/op/putscript.rs @@ -47,7 +47,7 @@ impl Session { // Check quota let access_token = self.state.access_token(); - let account_id = access_token.primary_id(); + let account_id = access_token.account_id(); self.server .has_available_quota(&access_token.as_resource_token(), script_bytes.len() as u64) .await diff --git a/crates/managesieve/src/op/renamescript.rs b/crates/managesieve/src/op/renamescript.rs index 1602532b..682f4aa1 100644 --- a/crates/managesieve/src/op/renamescript.rs +++ b/crates/managesieve/src/op/renamescript.rs @@ -49,7 +49,7 @@ impl Session { if name == new_name { return Ok(StatusResponse::ok("Old and new script names are the same.").into_bytes()); } - let account_id = self.state.access_token().primary_id(); + let account_id = self.state.access_token().account_id(); let document_id = self.get_script_id(account_id, &name).await?; if self.validate_name(account_id, &new_name).await?.is_some() { return Err(trc::ManageSieveEvent::Error diff --git a/crates/managesieve/src/op/setactive.rs b/crates/managesieve/src/op/setactive.rs index 325f0e35..2720ce47 100644 --- a/crates/managesieve/src/op/setactive.rs +++ b/crates/managesieve/src/op/setactive.rs @@ -33,7 +33,7 @@ impl Session { })?; // De/activate script - let account_id = self.state.access_token().primary_id(); + let account_id = self.state.access_token().account_id(); let mut batch = BatchBuilder::new(); if !name.is_empty() { let document_id = self.get_script_id(account_id, &name).await?; diff --git a/crates/migration/Cargo.toml b/crates/migration/Cargo.toml index 1e4385c4..a402e64f 100644 --- a/crates/migration/Cargo.toml +++ b/crates/migration/Cargo.toml @@ -17,7 +17,7 @@ groupware = { path = "../groupware" } dav-proto = { path = "../dav-proto" } proc_macros = { path = "../utils/proc-macros" } mail-parser = { version = "0.11", features = ["full_encoding"] } -mail-auth = { version = "0.7.1", features = ["rkyv"] } +mail-auth = { path = "/Users/me/code/mail-auth", features = ["rkyv"] } smtp-proto = { version = "0.2", features = ["rkyv", "serde"] } sieve-rs = { version = "0.7", features = ["rkyv"] } calcard_latest = { package = "calcard", version = "0.3", features = ["rkyv"] } diff --git a/crates/pop3/src/op/authenticate.rs b/crates/pop3/src/op/authenticate.rs index b259f11d..d104146e 100644 --- a/crates/pop3/src/op/authenticate.rs +++ b/crates/pop3/src/op/authenticate.rs @@ -110,7 +110,7 @@ impl Session { }; // Fetch mailbox - let mailbox = self.fetch_mailbox(access_token.primary_id()).await?; + let mailbox = self.fetch_mailbox(access_token.account_id()).await?; // Create session self.state = State::Authenticated { diff --git a/crates/smtp/Cargo.toml b/crates/smtp/Cargo.toml index a5b0d000..c4fb4661 100644 --- a/crates/smtp/Cargo.toml +++ b/crates/smtp/Cargo.toml @@ -20,7 +20,7 @@ common = { path = "../common" } email = { path = "../email" } spam-filter = { path = "../spam-filter" } trc = { path = "../trc" } -mail-auth = { version = "0.7.1", features = ["rkyv"] } +mail-auth = { path = "/Users/me/code/mail-auth", features = ["rkyv"] } mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } mail-parser = { version = "0.11", features = ["full_encoding"] } mail-builder = { version = "0.4" } diff --git a/crates/spam-filter/Cargo.toml b/crates/spam-filter/Cargo.toml index 9d75ede9..99210e00 100644 --- a/crates/spam-filter/Cargo.toml +++ b/crates/spam-filter/Cargo.toml @@ -13,7 +13,7 @@ common = { path = "../common" } smtp-proto = { version = "0.2", features = ["rkyv"] } mail-parser = { version = "0.11", features = ["full_encoding"] } mail-builder = { version = "0.4" } -mail-auth = { version = "0.7.1" } +mail-auth = { path = "/Users/me/code/mail-auth" } mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } tokio = { version = "1.47", features = ["net", "macros"] } psl = "2" diff --git a/crates/store/src/dispatch/lookup.rs b/crates/store/src/dispatch/lookup.rs index 1fe54ce8..6fbeb1f5 100644 --- a/crates/store/src/dispatch/lookup.rs +++ b/crates/store/src/dispatch/lookup.rs @@ -515,6 +515,13 @@ impl InMemoryStore { _ => false, } } + + pub fn as_store(&self) -> Option<&Store> { + match self { + InMemoryStore::Store(store) => Some(store), + _ => None, + } + } } pub enum LookupKey<'x> { diff --git a/crates/trc/Cargo.toml b/crates/trc/Cargo.toml index 226b4f00..8970574a 100644 --- a/crates/trc/Cargo.toml +++ b/crates/trc/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" [dependencies] event_macro = { path = "./event-macro" } -mail-auth = { version = "0.7.1" } +mail-auth = { path = "/Users/me/code/mail-auth" } mail-parser = { version = "0.11", features = ["full_encoding"] } base64 = "0.22.1" serde = "1.0" diff --git a/crates/trc/src/event/conv.rs b/crates/trc/src/event/conv.rs index 0cfad7a2..ecd1afda 100644 --- a/crates/trc/src/event/conv.rs +++ b/crates/trc/src/event/conv.rs @@ -367,7 +367,7 @@ impl From<&mail_auth::IprevOutput> for Error { Key::Details, value.ptr.as_ref().map(|s| { s.iter() - .map(|v| Value::String(v.into())) + .map(|v| Value::String(v.as_ref().into())) .collect::>() }), ) diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 0b76d4ba..8b20709d 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -11,7 +11,7 @@ rustls-pki-types = { version = "1" } tokio = { version = "1.47", features = ["net", "macros", "signal"] } tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] } serde = { version = "1.0", features = ["derive"]} -mail-auth = { version = "0.7.1" } +mail-auth = { path = "/Users/me/code/mail-auth" } smtp-proto = { version = "0.2" } mail-send = { version = "0.5", default-features = false, features = ["cram-md5", "ring", "tls12"] } ahash = { version = "0.8", features = ["serde"] } @@ -39,6 +39,7 @@ compact_str = "0.9.0" xxhash-rust = { version = "0.8.5", features = ["xxh3"] } farmhash = "1.1.5" nohash-hasher = "0.2.0" +arcstr = "1.2.0" [target.'cfg(unix)'.dependencies] privdrop = "0.5.3" diff --git a/crates/utils/src/cache.rs b/crates/utils/src/cache.rs index ca1d26b6..801b0eb9 100644 --- a/crates/utils/src/cache.rs +++ b/crates/utils/src/cache.rs @@ -4,6 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ +use arcstr::ArcStr; use mail_auth::{MX, ResolverCache, Txt}; use quick_cache::{ Equivalent, Weighter, @@ -71,6 +72,13 @@ impl Cache { self.0.insert(key, value); } + #[inline(always)] + pub fn update(&self, key: K, value: V) { + if let Err((key, value)) = self.0.replace(key, value, true) { + self.0.insert(key, value); + } + } + #[inline(always)] pub fn remove(&self, key: &Q) -> Option where @@ -220,9 +228,15 @@ impl CacheItemWeight for Box { } } -impl CacheItemWeight for Box<[T]> { +impl CacheItemWeight for Box<[T]> { fn weight(&self) -> u64 { - (self.len() * std::mem::size_of::()) as u64 + std::mem::size_of::>() as u64 + std::mem::size_of::>() as u64 + self.iter().map(|item| item.weight()).sum::() + } +} + +impl CacheItemWeight for Arc<[T]> { + fn weight(&self) -> u64 { + std::mem::size_of::>() as u64 + self.iter().map(|item| item.weight()).sum::() } } @@ -232,45 +246,28 @@ impl CacheItemWeight for u32 { } } -impl CacheItemWeight for Vec { +impl CacheItemWeight for IpAddr { fn weight(&self) -> u64 { - (self.len() * std::mem::size_of::()) as u64 - + std::mem::size_of::>() as u64 + std::mem::size_of::>() as u64 } } -impl CacheItemWeight for Vec { +impl CacheItemWeight for Ipv4Addr { fn weight(&self) -> u64 { - (self.len() * std::mem::size_of::()) as u64 - + std::mem::size_of::>() as u64 + std::mem::size_of::>() as u64 } } -impl CacheItemWeight for Vec { +impl CacheItemWeight for Ipv6Addr { fn weight(&self) -> u64 { - (self.len() * std::mem::size_of::()) as u64 - + std::mem::size_of::>() as u64 + std::mem::size_of::>() as u64 } } -impl CacheItemWeight for Vec { +impl CacheItemWeight for MX { fn weight(&self) -> u64 { - self.iter() - .map(|mx| { - mx.exchanges - .iter() - .map(|e| e.len() + std::mem::size_of::()) - .sum::() - }) - .sum::() as u64 - + std::mem::size_of::>() as u64 - } -} - -impl CacheItemWeight for Vec { - fn weight(&self) -> u64 { - self.iter().map(|s| s.len()).sum::() as u64 - + std::mem::size_of::>() as u64 + self.exchanges.iter().map(|e| e.len() as u64).sum::() + + std::mem::size_of::() as u64 } } @@ -280,18 +277,24 @@ impl CacheItemWeight for Txt { } } -impl CacheItemWeight for IpAddr { - fn weight(&self) -> u64 { - std::mem::size_of::() as u64 - } -} - impl CacheItemWeight for bool { fn weight(&self) -> u64 { std::mem::size_of::() as u64 } } +impl CacheItemWeight for ArcStr { + fn weight(&self) -> u64 { + self.len() as u64 + std::mem::size_of::() as u64 + } +} + +impl CacheItemWeight for () { + fn weight(&self) -> u64 { + 0 + } +} + impl TtlEntry { pub fn new(value: T, expires: Duration) -> Self { Self {