Authenticate using registry - part 1
This commit is contained in:
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -142,6 +142,12 @@ dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arcstr"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03918c3dbd7701a85c6b9887732e2921175f26c350b4563841d0958c21d57e6d"
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.5.3"
|
||||
@@ -1060,6 +1066,7 @@ dependencies = [
|
||||
"aes-gcm-siv",
|
||||
"ahash",
|
||||
"arc-swap",
|
||||
"arcstr",
|
||||
"base64 0.22.1",
|
||||
"bincode 2.0.1",
|
||||
"biscuit",
|
||||
@@ -1089,6 +1096,7 @@ dependencies = [
|
||||
"mail-send",
|
||||
"md5 0.8.0",
|
||||
"nlp",
|
||||
"nohash-hasher",
|
||||
"num_cpus",
|
||||
"opentelemetry",
|
||||
"opentelemetry-otlp",
|
||||
|
||||
@@ -73,10 +73,12 @@ num_cpus = "1.13.1"
|
||||
hashify = "0.2"
|
||||
rkyv = { version = "0.8.10", features = ["little_endian"] }
|
||||
indexmap = "2.7.1"
|
||||
tinyvec = "1.9.0"
|
||||
tinyvec = { version = "1.10.0", features = ["alloc"] }
|
||||
compact_str = { version = "0.9.0", features = ["rkyv", "serde"] }
|
||||
lz4_flex = { version = "0.12", features = ["frame"], default-features = false }
|
||||
hickory-proto = "0.24"
|
||||
arcstr = "1.2.0"
|
||||
nohash-hasher = "0.2.0"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
privdrop = "0.5.3"
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
use directory::Directory;
|
||||
use registry::schema::enums::ExpressionVariable;
|
||||
use std::borrow::Cow;
|
||||
use utils::config::{Config, utils::AsKey};
|
||||
|
||||
use crate::{
|
||||
Server,
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use super::{AccessToken, ResourceToken, TenantInfo, roles::RolePermissions};
|
||||
use super::{AccessToken, ResourceToken, TenantInfo, roles::PermissionsGroup};
|
||||
use crate::{
|
||||
Server,
|
||||
ipc::BroadcastEvent,
|
||||
listener::limiter::{ConcurrencyLimiter, LimiterResult},
|
||||
};
|
||||
use ahash::AHashSet;
|
||||
use registry::schema::enums::Permission;
|
||||
use std::{
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
sync::Arc,
|
||||
@@ -34,7 +35,7 @@ impl Server {
|
||||
principal: Principal,
|
||||
revision: u64,
|
||||
) -> trc::Result<AccessToken> {
|
||||
let mut role_permissions = RolePermissions::default();
|
||||
let mut role_permissions = PermissionsGroup::default();
|
||||
|
||||
// Extract data
|
||||
let mut object_quota = self.core.email.max_objects;
|
||||
|
||||
261
crates/common/src/auth/authentication.rs
Normal file
261
crates/common/src/auth/authentication.rs
Normal file
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
impl Server {
|
||||
pub async fn authenticate(&self, req: &AuthRequest<'_>) -> trc::Result<Arc<AccessToken>> {
|
||||
// Resolve directory
|
||||
let directory = req.directory.unwrap_or(&self.core.storage.directory);
|
||||
|
||||
// Validate credentials
|
||||
match &req.credentials {
|
||||
Credentials::OAuthBearer { token } if !directory.has_bearer_token_support() => {
|
||||
match self
|
||||
.validate_access_token(GrantType::AccessToken.into(), token)
|
||||
.await
|
||||
{
|
||||
Ok(token_into) => self.get_access_token(token_into.account_id).await,
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
_ => match self.authenticate_credentials(req, directory).await {
|
||||
Ok(principal) => self.get_access_token(principal).await,
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
}
|
||||
.and_then(|token| {
|
||||
token
|
||||
.assert_has_permission(Permission::Authenticate)
|
||||
.map(|_| token)
|
||||
})
|
||||
}
|
||||
|
||||
async fn authenticate_credentials(
|
||||
&self,
|
||||
req: &AuthRequest<'_>,
|
||||
directory: &Directory,
|
||||
) -> trc::Result<Principal> {
|
||||
// First try to authenticate the user against the default directory
|
||||
let result = match directory
|
||||
.query(
|
||||
QueryParams::credentials(&req.credentials)
|
||||
.with_return_member_of(req.return_member_of),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Some(principal)) => {
|
||||
trc::event!(
|
||||
Auth(trc::AuthEvent::Success),
|
||||
AccountName = principal.name().to_string(),
|
||||
AccountId = principal.id(),
|
||||
SpanId = req.session_id,
|
||||
);
|
||||
|
||||
return Ok(principal);
|
||||
}
|
||||
Ok(None) => Ok(()),
|
||||
Err(err) => {
|
||||
if err.matches(trc::EventType::Auth(trc::AuthEvent::MissingTotp)) {
|
||||
return Err(err);
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match &req.credentials {
|
||||
Credentials::Plain { username, secret } => {
|
||||
// Then check if the credentials match the fallback admin or master user
|
||||
let master_user: Option<(String, String)> = None;
|
||||
let todo = "implement master";
|
||||
match (&self.core.network.security.fallback_admin, &master_user) {
|
||||
(Some((fallback_admin, fallback_pass)), _) if username == fallback_admin => {
|
||||
if verify_secret_hash(fallback_pass, secret).await? {
|
||||
trc::event!(
|
||||
Auth(trc::AuthEvent::Success),
|
||||
AccountName = username.clone(),
|
||||
SpanId = req.session_id,
|
||||
);
|
||||
|
||||
return Ok(Principal::fallback_admin(fallback_pass));
|
||||
}
|
||||
}
|
||||
(_, Some((master_user, master_pass))) if username.ends_with(master_user) => {
|
||||
if verify_secret_hash(master_pass, secret).await? {
|
||||
let username = username.strip_suffix(master_user).unwrap();
|
||||
let username = username.strip_suffix('%').unwrap_or(username);
|
||||
|
||||
if let Some(principal) = directory
|
||||
.query(
|
||||
QueryParams::name(username)
|
||||
.with_return_member_of(req.return_member_of),
|
||||
)
|
||||
.await?
|
||||
{
|
||||
trc::event!(
|
||||
Auth(trc::AuthEvent::Success),
|
||||
AccountName = username.to_string(),
|
||||
SpanId = req.session_id,
|
||||
AccountId = principal.id(),
|
||||
Type = principal.typ().description(),
|
||||
);
|
||||
|
||||
return Ok(principal);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Validate API credentials
|
||||
if req.allow_api_access
|
||||
&& let Ok(Some(principal)) = self
|
||||
.store()
|
||||
.query(
|
||||
QueryParams::credentials(&req.credentials)
|
||||
.with_return_member_of(req.return_member_of),
|
||||
)
|
||||
.await
|
||||
&& principal.typ == Type::ApiKey
|
||||
{
|
||||
trc::event!(
|
||||
Auth(trc::AuthEvent::Success),
|
||||
AccountName = principal.name().to_string(),
|
||||
AccountId = principal.id(),
|
||||
SpanId = req.session_id,
|
||||
);
|
||||
|
||||
return Ok(principal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Credentials::OAuthBearer { token } if directory.has_bearer_token_support() => {
|
||||
// Check for bearer tokens issued locally
|
||||
if let Ok(token_info) = self
|
||||
.validate_access_token(GrantType::AccessToken.into(), token)
|
||||
.await
|
||||
{
|
||||
let principal = if token_info.account_id != FALLBACK_ADMIN_ID {
|
||||
directory
|
||||
.query(
|
||||
QueryParams::id(token_info.account_id)
|
||||
.with_return_member_of(req.return_member_of),
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
} else if let Some((_, fallback_pass)) =
|
||||
&self.core.network.security.fallback_admin
|
||||
{
|
||||
Principal::fallback_admin(fallback_pass).into()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(principal) = principal {
|
||||
trc::event!(
|
||||
Auth(trc::AuthEvent::Success),
|
||||
AccountName = principal.name().to_string(),
|
||||
AccountId = principal.id(),
|
||||
SpanId = req.session_id,
|
||||
);
|
||||
|
||||
return Ok(principal);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
if let Err(err) = result {
|
||||
Err(err)
|
||||
} else if self.has_auth_fail2ban() {
|
||||
let login = req.credentials.login();
|
||||
if self.is_auth_fail2banned(req.remote_ip, login).await? {
|
||||
Err(trc::SecurityEvent::AuthenticationBan
|
||||
.into_err()
|
||||
.ctx(trc::Key::RemoteIp, req.remote_ip)
|
||||
.ctx_opt(trc::Key::AccountName, login.map(|s| s.to_string())))
|
||||
} else {
|
||||
Err(trc::AuthEvent::Failed
|
||||
.ctx(trc::Key::RemoteIp, req.remote_ip)
|
||||
.ctx_opt(trc::Key::AccountName, login.map(|s| s.to_string())))
|
||||
}
|
||||
} else {
|
||||
Err(trc::AuthEvent::Failed
|
||||
.ctx(trc::Key::RemoteIp, req.remote_ip)
|
||||
.ctx_opt(
|
||||
trc::Key::AccountName,
|
||||
req.credentials.login().map(|s| s.to_string()),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> AuthRequest<'x> {
|
||||
pub fn from_credentials(
|
||||
credentials: Credentials<String>,
|
||||
session_id: u64,
|
||||
remote_ip: IpAddr,
|
||||
) -> Self {
|
||||
Self {
|
||||
credentials,
|
||||
session_id,
|
||||
remote_ip,
|
||||
return_member_of: true,
|
||||
directory: None,
|
||||
allow_api_access: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_plain(
|
||||
user: impl Into<String>,
|
||||
pass: impl Into<String>,
|
||||
session_id: u64,
|
||||
remote_ip: IpAddr,
|
||||
) -> Self {
|
||||
Self::from_credentials(
|
||||
Credentials::Plain {
|
||||
username: user.into(),
|
||||
secret: pass.into(),
|
||||
},
|
||||
session_id,
|
||||
remote_ip,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn without_members(mut self) -> Self {
|
||||
self.return_member_of = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_directory(mut self, directory: &'x Directory) -> Self {
|
||||
self.directory = Some(directory);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_api_access(mut self, allow_api_access: bool) -> Self {
|
||||
self.allow_api_access = allow_api_access;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl CacheItemWeight for AccessToken {
|
||||
fn weight(&self) -> u64 {
|
||||
self.obj_size
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait CredentialsUsername {
|
||||
fn login(&self) -> Option<&str>;
|
||||
}
|
||||
|
||||
impl CredentialsUsername for Credentials<String> {
|
||||
fn login(&self) -> Option<&str> {
|
||||
match self {
|
||||
Credentials::Plain { username, .. } | Credentials::XOauth2 { username, .. } => {
|
||||
username.as_str().into()
|
||||
}
|
||||
Credentials::OAuthBearer { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,35 +4,183 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::{Server, listener::limiter::ConcurrencyLimiter};
|
||||
use mail_send::Credentials;
|
||||
use oauth::GrantType;
|
||||
use std::{net::IpAddr, sync::Arc};
|
||||
use types::collection::Collection;
|
||||
use utils::{
|
||||
cache::CacheItemWeight,
|
||||
map::{bitmap::Bitmap, vec_map::VecMap},
|
||||
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},
|
||||
types::EnumType,
|
||||
};
|
||||
use std::{collections::HashMap, net::IpAddr, sync::Arc};
|
||||
use tinyvec::TinyVec;
|
||||
use trc::ipc::bitset::Bitset;
|
||||
use types::collection::Collection;
|
||||
use utils::{cache::CacheItemWeight, map::bitmap::Bitmap};
|
||||
|
||||
pub mod access_token;
|
||||
pub mod authentication;
|
||||
pub mod oauth;
|
||||
pub mod rate_limit;
|
||||
pub mod roles;
|
||||
pub mod sasl;
|
||||
|
||||
const PERMISSIONS_BITSET_SIZE: usize = Permission::COUNT.div_ceil(std::mem::size_of::<usize>());
|
||||
pub type Permissions = Bitset<PERMISSIONS_BITSET_SIZE>;
|
||||
pub type ObjectQuota = [u32; StorageObject::COUNT - 1];
|
||||
pub type IdMap<V> = HashMap<u32, V, nohash_hasher::BuildNoHashHasher<u32>>;
|
||||
|
||||
pub struct DirectoryEntries {
|
||||
pub emails: ArcSwap<EmailEntries>,
|
||||
pub domains: ArcSwap<DomainEntries>,
|
||||
pub accounts: ArcSwap<AccountEntries>,
|
||||
pub groups: ArcSwap<GroupEntries>,
|
||||
pub roles: ArcSwap<RoleEntries>,
|
||||
pub mailing_lists: ArcSwap<MailingListEntries>,
|
||||
pub tenants: ArcSwap<TenantEntries>,
|
||||
pub api_keys: ArcSwap<ApiKeyEntries>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EmailEntries {
|
||||
pub addresses: AHashMap<ArcStr, EmailEntry>,
|
||||
}
|
||||
|
||||
#[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<ArcStr, u32>,
|
||||
pub entries: IdMap<DomainEntry>,
|
||||
pub default: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AccountEntries {
|
||||
pub entries: IdMap<AccountEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GroupEntries {
|
||||
pub entries: IdMap<GroupEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RoleEntries {
|
||||
pub entries: IdMap<RoleEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MailingListEntries {
|
||||
pub entries: IdMap<MailingListEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TenantEntries {
|
||||
pub entries: IdMap<TenantEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ApiKeyEntries {
|
||||
pub entries: AHashMap<ArcStr, ApiKeyEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DomainEntry {
|
||||
pub name: ArcStr,
|
||||
pub id_alias_of: u32,
|
||||
pub id_tenant: u32,
|
||||
pub id_directory: u32,
|
||||
pub catch_all: Option<ArcStr>,
|
||||
pub sub_addressing_custom: Option<Arc<IfBlock>>,
|
||||
pub flags: u8,
|
||||
}
|
||||
|
||||
pub const DOMAIN_FLAG_LOCAL: u8 = 1;
|
||||
pub const DOMAIN_FLAG_DEFAULT: 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<ArcStr>,
|
||||
pub locale: Locale,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GroupEntry {
|
||||
pub addresses: Arc<[ArcStr]>,
|
||||
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<Arc<ObjectQuota>>,
|
||||
pub permissions: Option<Arc<PermissionsGroup>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RoleEntry {
|
||||
pub id_tenant: u32,
|
||||
pub id_roles: TinyVec<[u32; 3]>,
|
||||
pub permissions: Permissions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MailingListEntry {
|
||||
pub addresses: Arc<[ArcStr]>,
|
||||
pub id_tenant: u32,
|
||||
pub recipients: Arc<[ArcStr]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TenantEntry {
|
||||
pub id_roles: TinyVec<[u32; 3]>,
|
||||
pub quota_disk: u64,
|
||||
pub quota_objects: Option<Arc<ObjectQuota>>,
|
||||
pub permissions: Option<Arc<PermissionsGroup>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ApiKeyEntry {
|
||||
pub id: u32,
|
||||
pub id_tenant: u32,
|
||||
pub id_roles: TinyVec<[u32; 3]>,
|
||||
pub permissions: Option<Arc<PermissionsGroup>>,
|
||||
pub expires_at: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PermissionsGroup {
|
||||
pub enabled: Permissions,
|
||||
pub disabled: Permissions,
|
||||
pub merge: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AccessToken {
|
||||
pub addresses: Arc<[ArcStr]>,
|
||||
pub primary_id: u32,
|
||||
pub member_of: Vec<u32>,
|
||||
pub access_to: VecMap<u32, Bitmap<Collection>>,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub locale: Option<String>,
|
||||
pub emails: Vec<String>,
|
||||
pub quota: u64,
|
||||
pub object_quota: [u32; Collection::MAX],
|
||||
pub member_of: TinyVec<[u32; 3]>,
|
||||
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 tenant: Option<TenantInfo>,
|
||||
pub concurrent_http_requests: Option<ConcurrencyLimiter>,
|
||||
pub concurrent_imap_requests: Option<ConcurrencyLimiter>,
|
||||
pub concurrent_uploads: Option<ConcurrencyLimiter>,
|
||||
@@ -40,261 +188,18 @@ pub struct AccessToken {
|
||||
pub obj_size: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub struct TenantInfo {
|
||||
pub id: u32,
|
||||
pub quota: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ResourceToken {
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AccessTo {
|
||||
pub account_id: u32,
|
||||
pub quota: u64,
|
||||
pub tenant: Option<TenantInfo>,
|
||||
pub collections: Bitmap<Collection>,
|
||||
}
|
||||
|
||||
pub struct AuthRequest<'x> {
|
||||
credentials: Credentials<String>,
|
||||
pub struct AuthRequest {
|
||||
credentials: Credentials,
|
||||
session_id: u64,
|
||||
remote_ip: IpAddr,
|
||||
return_member_of: bool,
|
||||
allow_api_access: bool,
|
||||
directory: Option<&'x Directory>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub async fn authenticate(&self, req: &AuthRequest<'_>) -> trc::Result<Arc<AccessToken>> {
|
||||
// Resolve directory
|
||||
let directory = req.directory.unwrap_or(&self.core.storage.directory);
|
||||
|
||||
// Validate credentials
|
||||
match &req.credentials {
|
||||
Credentials::OAuthBearer { token } if !directory.has_bearer_token_support() => {
|
||||
match self
|
||||
.validate_access_token(GrantType::AccessToken.into(), token)
|
||||
.await
|
||||
{
|
||||
Ok(token_into) => self.get_access_token(token_into.account_id).await,
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
_ => match self.authenticate_credentials(req, directory).await {
|
||||
Ok(principal) => self.get_access_token(principal).await,
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
}
|
||||
.and_then(|token| {
|
||||
token
|
||||
.assert_has_permission(Permission::Authenticate)
|
||||
.map(|_| token)
|
||||
})
|
||||
}
|
||||
|
||||
async fn authenticate_credentials(
|
||||
&self,
|
||||
req: &AuthRequest<'_>,
|
||||
directory: &Directory,
|
||||
) -> trc::Result<Principal> {
|
||||
// First try to authenticate the user against the default directory
|
||||
let result = match directory
|
||||
.query(
|
||||
QueryParams::credentials(&req.credentials)
|
||||
.with_return_member_of(req.return_member_of),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Some(principal)) => {
|
||||
trc::event!(
|
||||
Auth(trc::AuthEvent::Success),
|
||||
AccountName = principal.name().to_string(),
|
||||
AccountId = principal.id(),
|
||||
SpanId = req.session_id,
|
||||
);
|
||||
|
||||
return Ok(principal);
|
||||
}
|
||||
Ok(None) => Ok(()),
|
||||
Err(err) => {
|
||||
if err.matches(trc::EventType::Auth(trc::AuthEvent::MissingTotp)) {
|
||||
return Err(err);
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match &req.credentials {
|
||||
Credentials::Plain { username, secret } => {
|
||||
// Then check if the credentials match the fallback admin or master user
|
||||
let master_user: Option<(String, String)> = None;
|
||||
let todo = "implement master";
|
||||
match (&self.core.network.security.fallback_admin, &master_user) {
|
||||
(Some((fallback_admin, fallback_pass)), _) if username == fallback_admin => {
|
||||
if verify_secret_hash(fallback_pass, secret).await? {
|
||||
trc::event!(
|
||||
Auth(trc::AuthEvent::Success),
|
||||
AccountName = username.clone(),
|
||||
SpanId = req.session_id,
|
||||
);
|
||||
|
||||
return Ok(Principal::fallback_admin(fallback_pass));
|
||||
}
|
||||
}
|
||||
(_, Some((master_user, master_pass))) if username.ends_with(master_user) => {
|
||||
if verify_secret_hash(master_pass, secret).await? {
|
||||
let username = username.strip_suffix(master_user).unwrap();
|
||||
let username = username.strip_suffix('%').unwrap_or(username);
|
||||
|
||||
if let Some(principal) = directory
|
||||
.query(
|
||||
QueryParams::name(username)
|
||||
.with_return_member_of(req.return_member_of),
|
||||
)
|
||||
.await?
|
||||
{
|
||||
trc::event!(
|
||||
Auth(trc::AuthEvent::Success),
|
||||
AccountName = username.to_string(),
|
||||
SpanId = req.session_id,
|
||||
AccountId = principal.id(),
|
||||
Type = principal.typ().description(),
|
||||
);
|
||||
|
||||
return Ok(principal);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Validate API credentials
|
||||
if req.allow_api_access
|
||||
&& let Ok(Some(principal)) = self
|
||||
.store()
|
||||
.query(
|
||||
QueryParams::credentials(&req.credentials)
|
||||
.with_return_member_of(req.return_member_of),
|
||||
)
|
||||
.await
|
||||
&& principal.typ == Type::ApiKey
|
||||
{
|
||||
trc::event!(
|
||||
Auth(trc::AuthEvent::Success),
|
||||
AccountName = principal.name().to_string(),
|
||||
AccountId = principal.id(),
|
||||
SpanId = req.session_id,
|
||||
);
|
||||
|
||||
return Ok(principal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Credentials::OAuthBearer { token } if directory.has_bearer_token_support() => {
|
||||
// Check for bearer tokens issued locally
|
||||
if let Ok(token_info) = self
|
||||
.validate_access_token(GrantType::AccessToken.into(), token)
|
||||
.await
|
||||
{
|
||||
let principal = if token_info.account_id != FALLBACK_ADMIN_ID {
|
||||
directory
|
||||
.query(
|
||||
QueryParams::id(token_info.account_id)
|
||||
.with_return_member_of(req.return_member_of),
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
} else if let Some((_, fallback_pass)) =
|
||||
&self.core.network.security.fallback_admin
|
||||
{
|
||||
Principal::fallback_admin(fallback_pass).into()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(principal) = principal {
|
||||
trc::event!(
|
||||
Auth(trc::AuthEvent::Success),
|
||||
AccountName = principal.name().to_string(),
|
||||
AccountId = principal.id(),
|
||||
SpanId = req.session_id,
|
||||
);
|
||||
|
||||
return Ok(principal);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
if let Err(err) = result {
|
||||
Err(err)
|
||||
} else if self.has_auth_fail2ban() {
|
||||
let login = req.credentials.login();
|
||||
if self.is_auth_fail2banned(req.remote_ip, login).await? {
|
||||
Err(trc::SecurityEvent::AuthenticationBan
|
||||
.into_err()
|
||||
.ctx(trc::Key::RemoteIp, req.remote_ip)
|
||||
.ctx_opt(trc::Key::AccountName, login.map(|s| s.to_string())))
|
||||
} else {
|
||||
Err(trc::AuthEvent::Failed
|
||||
.ctx(trc::Key::RemoteIp, req.remote_ip)
|
||||
.ctx_opt(trc::Key::AccountName, login.map(|s| s.to_string())))
|
||||
}
|
||||
} else {
|
||||
Err(trc::AuthEvent::Failed
|
||||
.ctx(trc::Key::RemoteIp, req.remote_ip)
|
||||
.ctx_opt(
|
||||
trc::Key::AccountName,
|
||||
req.credentials.login().map(|s| s.to_string()),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> AuthRequest<'x> {
|
||||
pub fn from_credentials(
|
||||
credentials: Credentials<String>,
|
||||
session_id: u64,
|
||||
remote_ip: IpAddr,
|
||||
) -> Self {
|
||||
Self {
|
||||
credentials,
|
||||
session_id,
|
||||
remote_ip,
|
||||
return_member_of: true,
|
||||
directory: None,
|
||||
allow_api_access: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_plain(
|
||||
user: impl Into<String>,
|
||||
pass: impl Into<String>,
|
||||
session_id: u64,
|
||||
remote_ip: IpAddr,
|
||||
) -> Self {
|
||||
Self::from_credentials(
|
||||
Credentials::Plain {
|
||||
username: user.into(),
|
||||
secret: pass.into(),
|
||||
},
|
||||
session_id,
|
||||
remote_ip,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn without_members(mut self) -> Self {
|
||||
self.return_member_of = false;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_directory(mut self, directory: &'x Directory) -> Self {
|
||||
self.directory = Some(directory);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_api_access(mut self, allow_api_access: bool) -> Self {
|
||||
self.allow_api_access = allow_api_access;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl CacheItemWeight for AccessToken {
|
||||
@@ -302,18 +207,3 @@ impl CacheItemWeight for AccessToken {
|
||||
self.obj_size
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) trait CredentialsUsername {
|
||||
fn login(&self) -> Option<&str>;
|
||||
}
|
||||
|
||||
impl CredentialsUsername for Credentials<String> {
|
||||
fn login(&self) -> Option<&str> {
|
||||
match self {
|
||||
Credentials::Plain { username, .. } | Credentials::XOauth2 { username, .. } => {
|
||||
username.as_str().into()
|
||||
}
|
||||
Credentials::OAuthBearer { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::{
|
||||
KV_RATE_LIMIT_HTTP_ANONYMOUS, KV_RATE_LIMIT_HTTP_AUTHENTICATED, Server, ip_to_bytes,
|
||||
listener::limiter::{InFlight, LimiterResult},
|
||||
};
|
||||
use registry::schema::enums::Permission;
|
||||
use std::net::IpAddr;
|
||||
use trc::AddContext;
|
||||
|
||||
|
||||
@@ -4,25 +4,17 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use crate::Server;
|
||||
use crate::{
|
||||
Server,
|
||||
auth::{Permissions, PermissionsGroup},
|
||||
};
|
||||
use ahash::AHashSet;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use trc::AddContext;
|
||||
use utils::cache::CacheItemWeight;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct RolePermissions {
|
||||
pub enabled: Permissions,
|
||||
pub disabled: Permissions,
|
||||
}
|
||||
|
||||
static USER_PERMISSIONS: LazyLock<Arc<RolePermissions>> = LazyLock::new(user_permissions);
|
||||
static ADMIN_PERMISSIONS: LazyLock<Arc<RolePermissions>> = LazyLock::new(admin_permissions);
|
||||
static TENANT_ADMIN_PERMISSIONS: LazyLock<Arc<RolePermissions>> =
|
||||
LazyLock::new(tenant_admin_permissions);
|
||||
|
||||
impl Server {
|
||||
pub async fn get_role_permissions(&self, role_id: u32) -> trc::Result<Arc<RolePermissions>> {
|
||||
pub async fn get_role_permissions(&self, role_id: u32) -> trc::Result<Arc<PermissionsGroup>> {
|
||||
match role_id {
|
||||
ROLE_USER => Ok(USER_PERMISSIONS.clone()),
|
||||
ROLE_ADMIN => Ok(ADMIN_PERMISSIONS.clone()),
|
||||
@@ -46,11 +38,11 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_role_permissions(&self, role_id: u32) -> trc::Result<Arc<RolePermissions>> {
|
||||
async fn build_role_permissions(&self, role_id: u32) -> trc::Result<Arc<PermissionsGroup>> {
|
||||
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 = RolePermissions::default();
|
||||
let mut return_permissions = PermissionsGroup::default();
|
||||
|
||||
'outer: loop {
|
||||
if let Some(role_id) = role_ids.next() {
|
||||
@@ -86,7 +78,7 @@ impl Server {
|
||||
if let Some(role_permissions) = self.inner.cache.permissions.get(&role_id) {
|
||||
return_permissions.union(role_permissions.as_ref());
|
||||
} else {
|
||||
let mut role_permissions = RolePermissions::default();
|
||||
let mut role_permissions = PermissionsGroup::default();
|
||||
|
||||
// Obtain principal
|
||||
let principal = self
|
||||
@@ -143,8 +135,8 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
impl RolePermissions {
|
||||
pub fn union(&mut self, other: &RolePermissions) {
|
||||
impl PermissionsGroup {
|
||||
pub fn union(&mut self, other: &PermissionsGroup) {
|
||||
self.enabled.union(&other.enabled);
|
||||
self.disabled.union(&other.disabled);
|
||||
}
|
||||
@@ -160,42 +152,3 @@ impl RolePermissions {
|
||||
enabled
|
||||
}
|
||||
}
|
||||
|
||||
fn tenant_admin_permissions() -> Arc<RolePermissions> {
|
||||
let mut permissions = RolePermissions::default();
|
||||
|
||||
for permission_id in 0..Permission::COUNT {
|
||||
let permission = Permission::from_id(permission_id as u32).unwrap();
|
||||
if permission.is_tenant_admin_permission() {
|
||||
permissions.enabled.set(permission_id);
|
||||
}
|
||||
}
|
||||
|
||||
Arc::new(permissions)
|
||||
}
|
||||
|
||||
fn user_permissions() -> Arc<RolePermissions> {
|
||||
let mut permissions = RolePermissions::default();
|
||||
|
||||
for permission_id in 0..Permission::COUNT {
|
||||
let permission = Permission::from_id(permission_id as u32).unwrap();
|
||||
if permission.is_user_permission() {
|
||||
permissions.enabled.set(permission_id);
|
||||
}
|
||||
}
|
||||
|
||||
Arc::new(permissions)
|
||||
}
|
||||
|
||||
fn admin_permissions() -> Arc<RolePermissions> {
|
||||
Arc::new(RolePermissions {
|
||||
enabled: Permissions::all(),
|
||||
disabled: Permissions::new(),
|
||||
})
|
||||
}
|
||||
|
||||
impl CacheItemWeight for RolePermissions {
|
||||
fn weight(&self) -> u64 {
|
||||
std::mem::size_of::<RolePermissions>() as u64
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use super::server::tls::build_self_signed_cert;
|
||||
use crate::{
|
||||
CacheSwap, Caches, Data, DavResource, DavResources, MailboxCache, MessageStoreCache,
|
||||
MessageUidCache, TlsConnectors,
|
||||
auth::{AccessToken, roles::RolePermissions},
|
||||
auth::AccessToken,
|
||||
config::{
|
||||
mailstore::spamfilter::SpamClassifier,
|
||||
server::tls::parse_certificates,
|
||||
@@ -40,7 +40,7 @@ impl Data {
|
||||
let mut subject_names = AHashSet::new();
|
||||
parse_certificates(bp, &mut certificates, &mut subject_names);
|
||||
if subject_names.is_empty() {
|
||||
subject_names.insert("localhost".to_string());
|
||||
subject_names.insert("localhost".into());
|
||||
}
|
||||
|
||||
// Build and test snowflake id generator
|
||||
@@ -56,7 +56,10 @@ impl Data {
|
||||
spam_classifier: ArcSwap::from_pointee(SpamClassifier::default()),
|
||||
tls_certificates: ArcSwap::from_pointee(certificates),
|
||||
tls_self_signed_cert: build_self_signed_cert(
|
||||
subject_names.into_iter().collect::<Vec<_>>(),
|
||||
subject_names
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.or_else(|err| {
|
||||
bp.build_error(
|
||||
@@ -79,6 +82,7 @@ impl Data {
|
||||
logos: Default::default(),
|
||||
smtp_connectors: TlsConnectors::default(),
|
||||
asn_geo_data: Default::default(),
|
||||
lookup_stores: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,10 +97,6 @@ impl Caches {
|
||||
(std::mem::size_of::<AccessToken>() + 255) as u64,
|
||||
),
|
||||
http_auth: Cache::new(cache.http_auth, (50 + std::mem::size_of::<u32>()) as u64),
|
||||
permissions: Cache::new(
|
||||
cache.permissions,
|
||||
std::mem::size_of::<RolePermissions>() as u64,
|
||||
),
|
||||
messages: Cache::new(
|
||||
cache.messages,
|
||||
(std::mem::size_of::<u32>()
|
||||
@@ -155,11 +155,11 @@ impl Caches {
|
||||
) -> Parameters<
|
||||
'_,
|
||||
T,
|
||||
CacheWithTtl<String, Txt>,
|
||||
CacheWithTtl<String, Arc<Vec<MX>>>,
|
||||
CacheWithTtl<String, Arc<Vec<Ipv4Addr>>>,
|
||||
CacheWithTtl<String, Arc<Vec<Ipv6Addr>>>,
|
||||
CacheWithTtl<IpAddr, Arc<Vec<String>>>,
|
||||
CacheWithTtl<Box<str>, Txt>,
|
||||
CacheWithTtl<Box<str>, Arc<Box<[MX]>>>,
|
||||
CacheWithTtl<Box<str>, Arc<Box<[Ipv4Addr]>>>,
|
||||
CacheWithTtl<Box<str>, Arc<Box<[Ipv6Addr]>>>,
|
||||
CacheWithTtl<IpAddr, Arc<Box<[Box<str>]>>>,
|
||||
> {
|
||||
Parameters {
|
||||
params,
|
||||
@@ -187,6 +187,7 @@ impl Default for Data {
|
||||
logos: Default::default(),
|
||||
smtp_connectors: Default::default(),
|
||||
asn_geo_data: Default::default(),
|
||||
lookup_stores: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use store::{
|
||||
write::SearchIndex,
|
||||
};
|
||||
use types::{collection::Collection, special_use::SpecialUse};
|
||||
use utils::config::cron::SimpleCron;
|
||||
use utils::cron::SimpleCron;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EmailConfig {
|
||||
|
||||
@@ -26,7 +26,7 @@ use std::{
|
||||
};
|
||||
use store::registry::{RegistryObject, bootstrap::Bootstrap};
|
||||
use tokio::net::lookup_host;
|
||||
use utils::{cache::CacheItemWeight, config::utils::ParseValue, glob::GlobMap};
|
||||
use utils::{cache::CacheItemWeight, glob::GlobMap};
|
||||
|
||||
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize, Debug, Default)]
|
||||
pub enum SpamClassifier {
|
||||
@@ -522,21 +522,6 @@ impl SpamClassifier {
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseValue for Element {
|
||||
fn parse_value(value: &str) -> utils::config::Result<Self> {
|
||||
match value {
|
||||
"url" => Ok(Element::Url),
|
||||
"domain" => Ok(Element::Domain),
|
||||
"email" => Ok(Element::Email),
|
||||
"ip" => Ok(Element::Ip),
|
||||
"header" => Ok(Element::Header),
|
||||
"body" => Ok(Element::Body),
|
||||
"any" | "message" => Ok(Element::Any),
|
||||
other => Err(format!("Invalid type {other:?}.",)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Location {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
|
||||
@@ -197,8 +197,8 @@ impl Server {
|
||||
|
||||
pub(crate) async fn parse_certificates(
|
||||
bp: &mut Bootstrap,
|
||||
certificates: &mut AHashMap<String, Arc<CertifiedKey>>,
|
||||
subject_names: &mut AHashSet<String>,
|
||||
certificates: &mut AHashMap<Box<str>, Arc<CertifiedKey>>,
|
||||
subject_names: &mut AHashSet<Box<str>>,
|
||||
) {
|
||||
// Parse certificates
|
||||
for cert_obj in bp.list_infallible::<Certificate>().await {
|
||||
@@ -216,10 +216,10 @@ pub(crate) async fn parse_certificates(
|
||||
}) {
|
||||
Ok((_, parsed)) => {
|
||||
// Add CNs and SANs to the list of names
|
||||
let mut names = AHashSet::new();
|
||||
let mut names: AHashSet<Box<str>> = AHashSet::new();
|
||||
for name in parsed.subject().iter_common_name() {
|
||||
if let Ok(name) = name.as_str() {
|
||||
names.insert(name.to_string());
|
||||
names.insert(name.into());
|
||||
}
|
||||
}
|
||||
for ext in parsed.extensions() {
|
||||
@@ -227,14 +227,16 @@ pub(crate) async fn parse_certificates(
|
||||
ext.parsed_extension()
|
||||
{
|
||||
for name in &san.general_names {
|
||||
let name = match name {
|
||||
GeneralName::DNSName(name) => name.to_string(),
|
||||
let name: Box<str> = match name {
|
||||
GeneralName::DNSName(name) => (*name).into(),
|
||||
GeneralName::IPAddress(ip) => match ip.len() {
|
||||
4 => Ipv4Addr::from(<[u8; 4]>::try_from(*ip).unwrap())
|
||||
.to_string(),
|
||||
.to_string()
|
||||
.into(),
|
||||
16 => {
|
||||
Ipv6Addr::from(<[u8; 16]>::try_from(*ip).unwrap())
|
||||
.to_string()
|
||||
.into()
|
||||
}
|
||||
_ => continue,
|
||||
},
|
||||
@@ -248,7 +250,13 @@ pub(crate) async fn parse_certificates(
|
||||
}
|
||||
|
||||
// Add custom SNIs
|
||||
names.extend(cert_obj.object.subject_alternative_names);
|
||||
names.extend(
|
||||
cert_obj
|
||||
.object
|
||||
.subject_alternative_names
|
||||
.into_iter()
|
||||
.map(Into::into),
|
||||
);
|
||||
|
||||
// Add domain names
|
||||
subject_names.extend(names.iter().cloned());
|
||||
@@ -257,16 +265,14 @@ pub(crate) async fn parse_certificates(
|
||||
let cert = Arc::new(cert);
|
||||
for name in names {
|
||||
certificates.insert(
|
||||
name.strip_prefix("*.")
|
||||
.map(|name| name.to_string())
|
||||
.unwrap_or(name),
|
||||
name.strip_prefix("*.").map(Into::into).unwrap_or(name),
|
||||
cert.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
// Add default certificate
|
||||
if cert_obj.object.default {
|
||||
certificates.insert("*".to_string(), cert.clone());
|
||||
certificates.insert("*".into(), cert.clone());
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
|
||||
@@ -23,7 +23,6 @@ use registry::{
|
||||
};
|
||||
use rustls_pki_types::{PrivateKeyDer, PrivatePkcs1KeyDer, PrivatePkcs8KeyDer, pem::PemObject};
|
||||
use store::registry::bootstrap::Bootstrap;
|
||||
use utils::config::utils::ParseValue;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MailAuthConfig {
|
||||
@@ -419,14 +418,3 @@ impl VerifyStrategy {
|
||||
matches!(self, VerifyStrategy::Strict)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseValue for VerifyStrategy {
|
||||
fn parse_value(value: &str) -> Result<Self, String> {
|
||||
match value {
|
||||
"relaxed" => Ok(VerifyStrategy::Relaxed),
|
||||
"strict" => Ok(VerifyStrategy::Strict),
|
||||
"disable" | "disabled" | "never" | "none" => Ok(VerifyStrategy::Disable),
|
||||
_ => Err(format!("Invalid value {:?}.", value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ use std::{
|
||||
net::IpAddr,
|
||||
time::Duration,
|
||||
};
|
||||
use utils::config::utils::ParseValue;
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -600,7 +599,14 @@ impl<'x> TryFrom<Variable<'x>> for IpLookupStrategy {
|
||||
_ => Err(()),
|
||||
},
|
||||
Variable::String(value) => {
|
||||
IpLookupStrategy::parse_value(value.as_str()).map_err(|_| ())
|
||||
match value.as_str() {
|
||||
"ipv4_only" => Ok(IpLookupStrategy::Ipv4Only),
|
||||
"ipv6_only" => Ok(IpLookupStrategy::Ipv6Only),
|
||||
//"ipv4_and_ipv6" => IpLookupStrategy::Ipv4AndIpv6,
|
||||
"ipv6_then_ipv4" => Ok(IpLookupStrategy::Ipv6thenIpv4),
|
||||
"ipv4_then_ipv6" => Ok(IpLookupStrategy::Ipv4thenIpv6),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
@@ -726,18 +732,6 @@ impl Default for QueueName {
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseValue for QueueName {
|
||||
fn parse_value(value: &str) -> Result<Self, String> {
|
||||
if let Some(name) = QueueName::new(value.trim().as_bytes()) {
|
||||
Ok(name)
|
||||
} else {
|
||||
Err(format!(
|
||||
"Queue name '{value}' is too long. Maximum length is 8 bytes."
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for QueueName {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.as_str().fmt(f)
|
||||
|
||||
@@ -17,8 +17,7 @@ use registry::schema::{
|
||||
TlsReportSettings,
|
||||
},
|
||||
};
|
||||
use std::time::Duration;
|
||||
use utils::config::utils::ParseValue;
|
||||
use std::{str::FromStr, time::Duration};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ReportConfig {
|
||||
@@ -93,7 +92,7 @@ impl ReportConfig {
|
||||
addresses: report
|
||||
.inbound_report_addresses
|
||||
.iter()
|
||||
.filter_map(|addr| AddressMatch::parse_value(addr).ok())
|
||||
.filter_map(|addr| AddressMatch::from_str(addr).ok())
|
||||
.collect(),
|
||||
forward: report.inbound_report_forwarding,
|
||||
store: dr.hold_mta_reports_for.map(|d| d.into_inner()),
|
||||
@@ -215,18 +214,6 @@ impl ReportConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseValue for AggregateFrequency {
|
||||
fn parse_value(value: &str) -> Result<Self, String> {
|
||||
match value {
|
||||
"daily" | "day" => Ok(AggregateFrequency::Daily),
|
||||
"hourly" | "hour" => Ok(AggregateFrequency::Hourly),
|
||||
"weekly" | "week" => Ok(AggregateFrequency::Weekly),
|
||||
"never" | "disable" | "false" => Ok(AggregateFrequency::Never),
|
||||
_ => Err(format!("Invalid aggregate frequency value {:?}.", value,)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'x> TryFrom<Variable<'x>> for AggregateFrequency {
|
||||
type Error = ();
|
||||
|
||||
@@ -241,8 +228,10 @@ impl<'x> TryFrom<Variable<'x>> for AggregateFrequency {
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseValue for AddressMatch {
|
||||
fn parse_value(value: &str) -> Result<Self, String> {
|
||||
impl FromStr for AddressMatch {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
if let Some(value) = value.strip_prefix('*').map(|v| v.trim()) {
|
||||
if !value.is_empty() {
|
||||
return Ok(AddressMatch::EndsWith(value.to_lowercase()));
|
||||
|
||||
@@ -24,10 +24,11 @@ use std::{
|
||||
fmt::Display,
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
net::SocketAddr,
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
};
|
||||
use store::registry::bootstrap::Bootstrap;
|
||||
use utils::{cache::CacheItemWeight, config::utils::ParseValue};
|
||||
use utils::cache::CacheItemWeight;
|
||||
|
||||
pub struct Resolvers {
|
||||
pub dns: MessageAuthenticator,
|
||||
@@ -74,7 +75,7 @@ pub enum MxPattern {
|
||||
pub struct Policy {
|
||||
pub id: String,
|
||||
pub mode: Mode,
|
||||
pub mx: Vec<MxPattern>,
|
||||
pub mx: Box<[MxPattern]>,
|
||||
pub max_age: u64,
|
||||
}
|
||||
|
||||
@@ -304,8 +305,9 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseValue for Mode {
|
||||
fn parse_value(value: &str) -> Result<Self, String> {
|
||||
impl FromStr for Mode {
|
||||
type Err = String;
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
match value {
|
||||
"enforce" => Ok(Self::Enforce),
|
||||
"testing" | "test" => Ok(Self::Testing),
|
||||
|
||||
@@ -20,9 +20,9 @@ use registry::schema::{
|
||||
use smtp_proto::*;
|
||||
use std::{
|
||||
net::{SocketAddr, ToSocketAddrs},
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
};
|
||||
use utils::config::utils::ParseValue;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SessionConfig {
|
||||
@@ -386,8 +386,10 @@ impl SessionConfig {
|
||||
#[derive(Default)]
|
||||
pub struct Mechanism(u64);
|
||||
|
||||
impl ParseValue for Mechanism {
|
||||
fn parse_value(value: &str) -> Result<Self, String> {
|
||||
impl FromStr for Mechanism {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Mechanism(match value.to_ascii_uppercase().as_str() {
|
||||
"LOGIN" => AUTH_LOGIN,
|
||||
"PLAIN" => AUTH_PLAIN,
|
||||
@@ -496,7 +498,18 @@ impl<'x> TryFrom<Variable<'x>> for MtPriority {
|
||||
ExpressionConstant::Nsep => Ok(MtPriority::Nsep),
|
||||
_ => Err(()),
|
||||
},
|
||||
Variable::String(value) => MtPriority::parse_value(value.as_str()).map_err(|_| ()),
|
||||
Variable::String(value) => {
|
||||
let value = value.as_str();
|
||||
if value.eq_ignore_ascii_case("MIXER") {
|
||||
Ok(MtPriority::Mixer)
|
||||
} else if value.eq_ignore_ascii_case("STANAG4406") {
|
||||
Ok(MtPriority::Stanag4406)
|
||||
} else if value.eq_ignore_ascii_case("NSEP") {
|
||||
Ok(MtPriority::Nsep)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use ahash::AHashMap;
|
||||
use crate::auth::IdMap;
|
||||
use coordinator::Coordinator;
|
||||
use directory::Directory;
|
||||
use std::sync::Arc;
|
||||
use store::{BlobStore, InMemoryStore, PurgeSchedule, RegistryStore, SearchStore, Store};
|
||||
use store::{BlobStore, InMemoryStore, RegistryStore, SearchStore, Store};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Storage {
|
||||
@@ -18,12 +18,5 @@ pub struct Storage {
|
||||
pub fts: SearchStore,
|
||||
pub lookup: InMemoryStore,
|
||||
pub pubsub: Coordinator,
|
||||
pub directory: Arc<Directory>,
|
||||
pub directories: AHashMap<String, Arc<Directory>>,
|
||||
pub purge_schedules: Vec<PurgeSchedule>,
|
||||
|
||||
pub stores: AHashMap<String, Store>,
|
||||
pub blobs: AHashMap<String, BlobStore>,
|
||||
pub lookups: AHashMap<String, InMemoryStore>,
|
||||
pub ftss: AHashMap<String, SearchStore>,
|
||||
pub directories: IdMap<Arc<Directory>>,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use ahash::{AHashMap, AHashSet, HashSet};
|
||||
use ahash::{AHashMap, AHashSet};
|
||||
use base64::{Engine, engine::general_purpose::STANDARD};
|
||||
use hyper::HeaderMap;
|
||||
use opentelemetry::{InstrumentationScope, KeyValue, logs::LoggerProvider};
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
use crate::{
|
||||
Inner, Server,
|
||||
auth::{AccessToken, ResourceToken, TenantInfo},
|
||||
auth::AccessToken,
|
||||
config::{
|
||||
mailstore::spamfilter::SpamClassifier,
|
||||
smtp::{
|
||||
auth::{ArcSealer, DkimSigner},
|
||||
auth::DkimSigner,
|
||||
queue::{
|
||||
ConnectionStrategy, DEFAULT_QUEUE_NAME, MxConfig, QueueExpiry, QueueName,
|
||||
QueueStrategy, RequireOptional, RoutingStrategy, TlsStrategy, VirtualQueue,
|
||||
@@ -100,47 +100,6 @@ impl Server {
|
||||
self.core.storage.lookups.get(name)
|
||||
}
|
||||
|
||||
pub fn get_in_memory_store_or_default(&self, name: &str, session_id: u64) -> &InMemoryStore {
|
||||
self.core.storage.lookups.get(name).unwrap_or_else(|| {
|
||||
if !name.is_empty() {
|
||||
trc::event!(
|
||||
Eval(trc::EvalEvent::StoreNotFound),
|
||||
Id = name.to_string(),
|
||||
SpanId = session_id,
|
||||
);
|
||||
}
|
||||
|
||||
&self.core.storage.lookup
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_data_store(&self, name: &str, session_id: u64) -> &Store {
|
||||
self.core.storage.stores.get(name).unwrap_or_else(|| {
|
||||
if !name.is_empty() {
|
||||
trc::event!(
|
||||
Eval(trc::EvalEvent::StoreNotFound),
|
||||
Id = name.to_string(),
|
||||
SpanId = session_id,
|
||||
);
|
||||
}
|
||||
|
||||
&self.core.storage.data
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_arc_sealer(&self, name: &str, session_id: u64) -> Option<Arc<ArcSealer>> {
|
||||
todo!()
|
||||
/*self.resolve_signature(name).map(|s| s.sealer).or_else(|| {
|
||||
trc::event!(
|
||||
Arc(trc::ArcEvent::SealerNotFound),
|
||||
Id = name.to_string(),
|
||||
SpanId = session_id,
|
||||
);
|
||||
|
||||
None
|
||||
})*/
|
||||
}
|
||||
|
||||
pub fn get_dkim_signer(&self, name: &str, session_id: u64) -> Option<Arc<DkimSigner>> {
|
||||
todo!()
|
||||
/*self.resolve_signature(name).map(|s| s.signer).or_else(|| {
|
||||
|
||||
@@ -26,7 +26,6 @@ impl Server {
|
||||
|
||||
match fnc_id {
|
||||
F_IS_LOCAL_DOMAIN => {
|
||||
let directory = params.next_as_string();
|
||||
let domain = params.next_as_string();
|
||||
|
||||
self.get_directory_or_default(directory.as_ref(), session_id)
|
||||
@@ -36,7 +35,6 @@ impl Server {
|
||||
.map(|v| v.into())
|
||||
}
|
||||
F_IS_LOCAL_ADDRESS => {
|
||||
let directory = params.next_as_string();
|
||||
let address = params.next_as_string();
|
||||
|
||||
self.get_directory_or_default(directory.as_ref(), session_id)
|
||||
|
||||
@@ -93,8 +93,8 @@ pub const F_SQL_QUERY: u32 = 7;
|
||||
pub const F_DNS_QUERY: u32 = 8;
|
||||
|
||||
pub const ASYNC_FUNCTIONS: &[(&str, u32, u32)] = &[
|
||||
("is_local_domain", F_IS_LOCAL_DOMAIN, 2),
|
||||
("is_local_address", F_IS_LOCAL_ADDRESS, 2),
|
||||
("is_local_domain", F_IS_LOCAL_DOMAIN, 1),
|
||||
("is_local_address", F_IS_LOCAL_ADDRESS, 1),
|
||||
("key_get", F_KEY_GET, 2),
|
||||
("key_exists", F_KEY_EXISTS, 2),
|
||||
("key_set", F_KEY_SET, 3),
|
||||
|
||||
@@ -30,7 +30,7 @@ pub struct IfThen {
|
||||
pub struct IfBlock {
|
||||
pub id: Id,
|
||||
pub property: Property,
|
||||
pub if_then: Vec<IfThen>,
|
||||
pub if_then: Box<[IfThen]>,
|
||||
pub default: Expression,
|
||||
}
|
||||
|
||||
@@ -121,13 +121,9 @@ impl BootstrapExprExt for Bootstrap {
|
||||
expr: &structs::Expression,
|
||||
) -> Option<IfBlock> {
|
||||
// Parse conditions
|
||||
let mut if_block = IfBlock {
|
||||
id,
|
||||
property: expr_ctx.property,
|
||||
if_then: Vec::with_capacity(expr.match_.len()),
|
||||
default: Expression {
|
||||
let mut if_then = Vec::with_capacity(expr.match_.len());
|
||||
let mut default = Expression {
|
||||
items: Default::default(),
|
||||
},
|
||||
};
|
||||
|
||||
if expr.else_.is_empty() {
|
||||
@@ -160,7 +156,7 @@ impl BootstrapExprExt for Bootstrap {
|
||||
|
||||
match ExpressionParser::new(Tokenizer::new(&expr.else_, &token_map)).parse() {
|
||||
Ok(expr) => {
|
||||
if_block.default = expr;
|
||||
default = expr;
|
||||
}
|
||||
Err(err) => {
|
||||
self.invalid_property(
|
||||
@@ -177,7 +173,7 @@ impl BootstrapExprExt for Bootstrap {
|
||||
Ok(if_expr) => {
|
||||
match ExpressionParser::new(Tokenizer::new(&match_.then, &token_map)).parse() {
|
||||
Ok(then_expr) => {
|
||||
if_block.if_then.push(IfThen {
|
||||
if_then.push(IfThen {
|
||||
expr: if_expr,
|
||||
then: then_expr,
|
||||
});
|
||||
@@ -211,7 +207,12 @@ impl BootstrapExprExt for Bootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
Some(if_block)
|
||||
Some(IfBlock {
|
||||
id,
|
||||
property: expr_ctx.property,
|
||||
if_then: if_then.into_boxed_slice(),
|
||||
default,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,15 +6,18 @@
|
||||
|
||||
use compact_str::CompactString;
|
||||
use regex::Regex;
|
||||
use registry::schema::enums::{ExpressionConstant, ExpressionVariable};
|
||||
use registry::schema::{
|
||||
enums::{ExpressionConstant, ExpressionVariable},
|
||||
structs::Rate,
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::{Display, Formatter},
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr},
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
};
|
||||
use trc::MetricType;
|
||||
use utils::config::{Rate, utils::ParseValue};
|
||||
|
||||
pub mod eval;
|
||||
pub mod functions;
|
||||
@@ -23,8 +26,9 @@ pub mod parser;
|
||||
pub mod tokenizer;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Default)]
|
||||
#[repr(transparent)]
|
||||
pub struct Expression {
|
||||
pub items: Vec<ExpressionItem>,
|
||||
pub items: Box<[ExpressionItem]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -246,7 +250,7 @@ impl From<bool> for Variable<'_> {
|
||||
impl<T: Into<Constant>> From<T> for Expression {
|
||||
fn from(value: T) -> Self {
|
||||
Expression {
|
||||
items: vec![ExpressionItem::Constant(value.into())],
|
||||
items: Box::new([ExpressionItem::Constant(value.into())]),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -336,7 +340,9 @@ impl<'x> TryFrom<Variable<'x>> for Duration {
|
||||
Variable::Integer(value) if value > 0 => Ok(Duration::from_millis(value as u64)),
|
||||
Variable::Float(value) if value > 0.0 => Ok(Duration::from_millis(value as u64)),
|
||||
Variable::String(value) if !value.is_empty() => {
|
||||
Duration::parse_value(value.as_str()).map_err(|_| ())
|
||||
registry::types::duration::Duration::from_str(value.as_str())
|
||||
.map(|v| v.into_inner())
|
||||
.map_err(|_| ())
|
||||
}
|
||||
_ => Err(()),
|
||||
}
|
||||
@@ -433,8 +439,8 @@ impl<'x> TryFrom<Variable<'x>> for Rate {
|
||||
|
||||
if requests > 0 && period > 0 {
|
||||
Ok(Rate {
|
||||
requests: requests as u64,
|
||||
period: Duration::from_millis(period as u64),
|
||||
count: requests as u64,
|
||||
period: registry::types::duration::Duration::from_millis(period as u64),
|
||||
})
|
||||
} else {
|
||||
Err(())
|
||||
|
||||
@@ -11,9 +11,8 @@ use super::{
|
||||
use ahash::AHashSet;
|
||||
use regex::Regex;
|
||||
use registry::{schema::enums::ExpressionConstant, types::EnumType};
|
||||
use std::{borrow::Cow, iter::Peekable, slice::Iter, time::Duration};
|
||||
use std::{borrow::Cow, iter::Peekable, slice::Iter};
|
||||
use trc::MetricType;
|
||||
use utils::config::utils::ParseValue;
|
||||
|
||||
pub struct Tokenizer<'x> {
|
||||
pub(crate) iter: Peekable<Iter<'x, u8>>,
|
||||
@@ -376,7 +375,7 @@ impl<'x> Tokenizer<'x> {
|
||||
} else {
|
||||
Err(format!("Constant {:?} not allowed in this context", buf))
|
||||
}
|
||||
} else if let Ok(duration) = Duration::parse_value(&buf) {
|
||||
} else if let Ok(duration) = registry::types::duration::Duration::from_str(&buf) {
|
||||
Ok(Token::Constant(Constant::Integer(
|
||||
duration.as_millis() as i64
|
||||
)))
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#![warn(clippy::large_futures)]
|
||||
|
||||
use crate::{
|
||||
auth::DirectoryEntries,
|
||||
config::mailstore::{
|
||||
email::EmailConfig,
|
||||
imap::ImapConfig,
|
||||
@@ -18,7 +19,7 @@ use crate::{
|
||||
};
|
||||
use ahash::{AHashMap, AHashSet};
|
||||
use arc_swap::ArcSwap;
|
||||
use auth::{AccessToken, oauth::config::OAuthConfig, roles::RolePermissions};
|
||||
use auth::{AccessToken, oauth::config::OAuthConfig};
|
||||
use calcard::common::timezone::Tz;
|
||||
use config::{
|
||||
groupware::GroupwareConfig,
|
||||
@@ -43,7 +44,10 @@ use std::{
|
||||
sync::{Arc, atomic::AtomicBool},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use store::rand::{Rng, distr::Alphanumeric};
|
||||
use store::{
|
||||
InMemoryStore,
|
||||
rand::{Rng, distr::Alphanumeric},
|
||||
};
|
||||
use tinyvec::TinyVec;
|
||||
use tokio::sync::{Notify, Semaphore, mpsc};
|
||||
use tokio_rustls::TlsConnector;
|
||||
@@ -134,6 +138,7 @@ pub struct Server {
|
||||
pub struct Inner {
|
||||
pub shared_core: ArcSwap<Core>,
|
||||
pub data: Data,
|
||||
pub directory: DirectoryEntries,
|
||||
pub cache: Caches,
|
||||
pub ipc: Ipc,
|
||||
}
|
||||
@@ -141,10 +146,11 @@ pub struct Inner {
|
||||
pub struct Data {
|
||||
pub spam_classifier: ArcSwap<SpamClassifier>,
|
||||
|
||||
pub tls_certificates: ArcSwap<AHashMap<String, Arc<CertifiedKey>>>,
|
||||
pub tls_certificates: ArcSwap<AHashMap<Box<str>, Arc<CertifiedKey>>>,
|
||||
pub tls_self_signed_cert: Option<Arc<CertifiedKey>>,
|
||||
|
||||
pub blocked_ips: RwLock<BlockedIps>,
|
||||
pub lookup_stores: ArcSwap<AHashMap<Box<str>, InMemoryStore>>,
|
||||
|
||||
pub asn_geo_data: AsnGeoLookupData,
|
||||
|
||||
@@ -154,15 +160,14 @@ pub struct Data {
|
||||
pub queue_status: AtomicBool,
|
||||
|
||||
pub webadmin: WebAdminManager,
|
||||
pub logos: Mutex<AHashMap<String, Option<Resource<Vec<u8>>>>>,
|
||||
pub logos: Mutex<AHashMap<Box<str>, Option<Resource<Vec<u8>>>>>,
|
||||
|
||||
pub smtp_connectors: TlsConnectors,
|
||||
}
|
||||
|
||||
pub struct Caches {
|
||||
pub access_tokens: Cache<u32, Arc<AccessToken>>,
|
||||
pub http_auth: Cache<String, HttpAuthCache>,
|
||||
pub permissions: Cache<u32, Arc<RolePermissions>>,
|
||||
pub http_auth: Cache<Box<str>, HttpAuthCache>,
|
||||
|
||||
pub messages: Cache<u32, CacheSwap<MessageStoreCache>>,
|
||||
pub files: Cache<u32, CacheSwap<DavResources>>,
|
||||
@@ -170,14 +175,14 @@ pub struct Caches {
|
||||
pub events: Cache<u32, CacheSwap<DavResources>>,
|
||||
pub scheduling: Cache<u32, CacheSwap<DavResources>>,
|
||||
|
||||
pub dns_txt: CacheWithTtl<String, Txt>,
|
||||
pub dns_mx: CacheWithTtl<String, Arc<Vec<MX>>>,
|
||||
pub dns_ptr: CacheWithTtl<IpAddr, Arc<Vec<String>>>,
|
||||
pub dns_ipv4: CacheWithTtl<String, Arc<Vec<Ipv4Addr>>>,
|
||||
pub dns_ipv6: CacheWithTtl<String, Arc<Vec<Ipv6Addr>>>,
|
||||
pub dns_tlsa: CacheWithTtl<String, Arc<Tlsa>>,
|
||||
pub dns_mta_sts: CacheWithTtl<String, Arc<Policy>>,
|
||||
pub dns_rbl: CacheWithTtl<String, Option<Arc<IpResolver>>>,
|
||||
pub dns_txt: CacheWithTtl<Box<str>, Txt>,
|
||||
pub dns_mx: CacheWithTtl<Box<str>, Arc<Box<[MX]>>>,
|
||||
pub dns_ptr: CacheWithTtl<IpAddr, Arc<Box<[Box<str>]>>>,
|
||||
pub dns_ipv4: CacheWithTtl<Box<str>, Arc<Box<[Ipv4Addr]>>>,
|
||||
pub dns_ipv6: CacheWithTtl<Box<str>, Arc<Box<[Ipv6Addr]>>>,
|
||||
pub dns_tlsa: CacheWithTtl<Box<str>, Arc<Tlsa>>,
|
||||
pub dns_mta_sts: CacheWithTtl<Box<str>, Arc<Policy>>,
|
||||
pub dns_rbl: CacheWithTtl<Box<str>, Option<Arc<IpResolver>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -8,7 +8,6 @@ use super::AcmeProvider;
|
||||
use crate::Server;
|
||||
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
|
||||
use trc::AddContext;
|
||||
use utils::config::ConfigKey;
|
||||
|
||||
impl Server {
|
||||
pub(crate) async fn load_cert(&self, provider: &AcmeProvider) -> trc::Result<Option<Vec<u8>>> {
|
||||
|
||||
@@ -10,63 +10,63 @@ use std::sync::{
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConcurrencyLimiter {
|
||||
pub max_concurrent: u64,
|
||||
pub concurrent: Arc<AtomicU64>,
|
||||
#[repr(transparent)]
|
||||
pub struct ConcurrencyLimiter(Arc<ConcurrencyLimiterInner>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConcurrencyLimiterInner {
|
||||
max_concurrent: u64,
|
||||
concurrent: AtomicU64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InFlight {
|
||||
concurrent: Arc<AtomicU64>,
|
||||
}
|
||||
|
||||
pub enum LimiterResult {
|
||||
Allowed(InFlight),
|
||||
Forbidden,
|
||||
Disabled,
|
||||
}
|
||||
pub struct InFlight(Arc<ConcurrencyLimiterInner>);
|
||||
|
||||
impl Drop for InFlight {
|
||||
fn drop(&mut self) {
|
||||
self.concurrent.fetch_sub(1, Ordering::Relaxed);
|
||||
self.0.concurrent.fetch_sub(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
impl ConcurrencyLimiter {
|
||||
pub fn new(max_concurrent: u64) -> Self {
|
||||
ConcurrencyLimiter {
|
||||
ConcurrencyLimiter(Arc::new(ConcurrencyLimiterInner {
|
||||
max_concurrent,
|
||||
concurrent: Arc::new(0.into()),
|
||||
}
|
||||
concurrent: AtomicU64::new(0),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn is_allowed(&self) -> LimiterResult {
|
||||
if self.concurrent.load(Ordering::Relaxed) < self.max_concurrent {
|
||||
if self.0.concurrent.load(Ordering::Relaxed) < self.0.max_concurrent {
|
||||
// Return in-flight request
|
||||
self.concurrent.fetch_add(1, Ordering::Relaxed);
|
||||
LimiterResult::Allowed(InFlight {
|
||||
concurrent: self.concurrent.clone(),
|
||||
})
|
||||
self.0.concurrent.fetch_add(1, Ordering::Relaxed);
|
||||
LimiterResult::Allowed(InFlight(self.0.clone()))
|
||||
} else {
|
||||
LimiterResult::Forbidden
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_is_allowed(&self) -> bool {
|
||||
self.concurrent.load(Ordering::Relaxed) < self.max_concurrent
|
||||
self.0.concurrent.load(Ordering::Relaxed) < self.0.max_concurrent
|
||||
}
|
||||
|
||||
pub fn is_active(&self) -> bool {
|
||||
self.concurrent.load(Ordering::Relaxed) > 0
|
||||
self.0.concurrent.load(Ordering::Relaxed) > 0
|
||||
}
|
||||
}
|
||||
|
||||
impl InFlight {
|
||||
pub fn num_concurrent(&self) -> u64 {
|
||||
self.concurrent.load(Ordering::Relaxed)
|
||||
self.0.concurrent.load(Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum LimiterResult {
|
||||
Allowed(InFlight),
|
||||
Forbidden,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl From<LimiterResult> for Option<InFlight> {
|
||||
fn from(result: LimiterResult) -> Self {
|
||||
match result {
|
||||
|
||||
@@ -9,7 +9,10 @@ use crate::{
|
||||
backend::{ldap::LdapDirectory, oidc::OpenIdDirectory, sql::SqlDirectory},
|
||||
};
|
||||
use ahash::AHashMap;
|
||||
use registry::schema::structs;
|
||||
use registry::schema::{
|
||||
prelude::Object,
|
||||
structs::{self, Authentication},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use store::registry::bootstrap::Bootstrap;
|
||||
|
||||
@@ -37,6 +40,22 @@ impl Directories {
|
||||
}
|
||||
}
|
||||
|
||||
Directories { directories }
|
||||
let mut default_directory = None;
|
||||
let auth = bp.setting_infallible::<Authentication>().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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Directories {
|
||||
default_directory,
|
||||
directories,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ pub struct Group {
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct Directories {
|
||||
pub default_directory: Option<Arc<Directory>>,
|
||||
pub directories: AHashMap<Id, Arc<Directory>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ use types::{
|
||||
keyword::Keyword,
|
||||
special_use::SpecialUse,
|
||||
};
|
||||
use utils::config::utils::ParseValue;
|
||||
|
||||
struct SieveMessage<'x> {
|
||||
pub raw_message: Cow<'x, [u8]>,
|
||||
@@ -198,20 +197,15 @@ impl SieveScriptIngest for Server {
|
||||
} => {
|
||||
if !mailboxes.is_empty() {
|
||||
let mut special_use_ids = Vec::with_capacity(special_use.len());
|
||||
for role in special_use {
|
||||
special_use_ids.push(if role.eq_ignore_ascii_case("inbox") {
|
||||
INBOX_ID
|
||||
} else if role.eq_ignore_ascii_case("trash") {
|
||||
TRASH_ID
|
||||
} else {
|
||||
let mut mailbox_id = u32::MAX;
|
||||
if let Ok(role) = SpecialUse::parse_value(&role)
|
||||
&& let Some(m) = cache.mailbox_by_role(&role)
|
||||
{
|
||||
mailbox_id = m.document_id;
|
||||
}
|
||||
|
||||
mailbox_id
|
||||
for role in special_use.iter().map(|v| SpecialUse::parse(v)) {
|
||||
special_use_ids.push(match role {
|
||||
Some(SpecialUse::Inbox) => INBOX_ID,
|
||||
Some(SpecialUse::Trash) => TRASH_ID,
|
||||
Some(role) => cache
|
||||
.mailbox_by_role(&role)
|
||||
.map(|m| m.document_id)
|
||||
.unwrap_or(u32::MAX),
|
||||
None => u32::MAX,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -244,14 +238,11 @@ impl SieveScriptIngest for Server {
|
||||
} else if !special_use.is_empty() {
|
||||
let mut result = true;
|
||||
|
||||
for role in special_use {
|
||||
if !role.eq_ignore_ascii_case("inbox")
|
||||
&& !role.eq_ignore_ascii_case("trash")
|
||||
{
|
||||
let role = SpecialUse::parse_value(&role);
|
||||
if role.is_err()
|
||||
|| cache.mailbox_by_role(&role.unwrap()).is_none()
|
||||
{
|
||||
for role in special_use.iter().map(|v| SpecialUse::parse(v)) {
|
||||
match role {
|
||||
Some(SpecialUse::Inbox | SpecialUse::Trash) => {}
|
||||
Some(other) if cache.mailbox_by_role(&other).is_some() => {}
|
||||
_ => {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
@@ -334,19 +325,24 @@ impl SieveScriptIngest for Server {
|
||||
}
|
||||
|
||||
// Find mailbox by role
|
||||
if let Some(special_use) = special_use
|
||||
&& target_id == u32::MAX
|
||||
if target_id == u32::MAX
|
||||
&& let Some(special_use) =
|
||||
special_use.as_deref().and_then(SpecialUse::parse)
|
||||
{
|
||||
if special_use.eq_ignore_ascii_case("inbox") {
|
||||
match special_use {
|
||||
SpecialUse::Inbox => {
|
||||
target_id = INBOX_ID;
|
||||
} else if special_use.eq_ignore_ascii_case("trash") {
|
||||
}
|
||||
SpecialUse::Trash => {
|
||||
target_id = TRASH_ID;
|
||||
} else if let Ok(role) = SpecialUse::parse_value(&special_use)
|
||||
&& let Some(item) = cache.mailbox_by_role(&role)
|
||||
{
|
||||
}
|
||||
role => {
|
||||
if let Some(item) = cache.mailbox_by_role(&role) {
|
||||
target_id = item.document_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find mailbox by name
|
||||
if target_id == u32::MAX {
|
||||
|
||||
@@ -144,7 +144,7 @@ impl FromLegacy for Mailbox {
|
||||
role: legacy
|
||||
.get(&Property::Role)
|
||||
.as_string()
|
||||
.and_then(|r| SpecialUse::parse_value(r).ok())
|
||||
.and_then(SpecialUse::parse)
|
||||
.unwrap_or(SpecialUse::None),
|
||||
parent_id: legacy
|
||||
.get(&Property::ParentId)
|
||||
|
||||
@@ -15,6 +15,8 @@ pub mod ipmask;
|
||||
pub mod socketaddr;
|
||||
|
||||
pub trait EnumType: Sized {
|
||||
const COUNT: usize;
|
||||
|
||||
fn parse(s: &str) -> Option<Self>;
|
||||
fn as_str(&self) -> &'static str;
|
||||
fn from_id(id: u16) -> Option<Self>;
|
||||
|
||||
@@ -214,6 +214,18 @@ impl CacheItemWeight for String {
|
||||
}
|
||||
}
|
||||
|
||||
impl CacheItemWeight for Box<str> {
|
||||
fn weight(&self) -> u64 {
|
||||
self.len() as u64 + std::mem::size_of::<Box<str>>() as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> CacheItemWeight for Box<[T]> {
|
||||
fn weight(&self) -> u64 {
|
||||
(self.len() * std::mem::size_of::<T>()) as u64 + std::mem::size_of::<Box<[T]>>() as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl CacheItemWeight for u32 {
|
||||
fn weight(&self) -> u64 {
|
||||
std::mem::size_of::<u32>() as u64
|
||||
|
||||
Reference in New Issue
Block a user