Improved error handling (part 1)
This commit is contained in:
@@ -7,14 +7,18 @@
|
||||
use mail_send::Credentials;
|
||||
use smtp_proto::{AUTH_CRAM_MD5, AUTH_LOGIN, AUTH_OAUTHBEARER, AUTH_PLAIN, AUTH_XOAUTH2};
|
||||
|
||||
use crate::{DirectoryError, Principal, QueryBy};
|
||||
use crate::{IntoError, Principal, QueryBy};
|
||||
|
||||
use super::{ImapDirectory, ImapError};
|
||||
|
||||
impl ImapDirectory {
|
||||
pub async fn query(&self, query: QueryBy<'_>) -> crate::Result<Option<Principal<u32>>> {
|
||||
pub async fn query(&self, query: QueryBy<'_>) -> trc::Result<Option<Principal<u32>>> {
|
||||
if let QueryBy::Credentials(credentials) = query {
|
||||
let mut client = self.pool.get().await?;
|
||||
let mut client = self
|
||||
.pool
|
||||
.get()
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?;
|
||||
let mechanism = match credentials {
|
||||
Credentials::Plain { .. }
|
||||
if (client.mechanisms & (AUTH_PLAIN | AUTH_LOGIN | AUTH_CRAM_MD5)) != 0 =>
|
||||
@@ -34,13 +38,12 @@ impl ImapDirectory {
|
||||
AUTH_XOAUTH2
|
||||
}
|
||||
_ => {
|
||||
tracing::warn!(
|
||||
context = "remote",
|
||||
event = "error",
|
||||
protocol = "imap",
|
||||
"IMAP server does not offer any supported auth mechanisms.",
|
||||
);
|
||||
return Ok(None);
|
||||
trc::bail!(trc::Cause::Unsupported
|
||||
.ctx(
|
||||
trc::Key::Reason,
|
||||
"IMAP server does not offer any supported auth mechanisms."
|
||||
)
|
||||
.protocol(trc::Protocol::Imap));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -51,31 +54,41 @@ impl ImapDirectory {
|
||||
}
|
||||
Err(err) => match &err {
|
||||
ImapError::AuthenticationFailed => Ok(None),
|
||||
_ => Err(err.into()),
|
||||
_ => Err(err.into_error()),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
Err(DirectoryError::unsupported("imap", "query"))
|
||||
Err(trc::Cause::Unsupported
|
||||
.caused_by(trc::location!())
|
||||
.protocol(trc::Protocol::Imap))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn email_to_ids(&self, _address: &str) -> crate::Result<Vec<u32>> {
|
||||
Err(DirectoryError::unsupported("imap", "email_to_ids"))
|
||||
pub async fn email_to_ids(&self, _address: &str) -> trc::Result<Vec<u32>> {
|
||||
Err(trc::Cause::Unsupported
|
||||
.caused_by(trc::location!())
|
||||
.protocol(trc::Protocol::Imap))
|
||||
}
|
||||
|
||||
pub async fn rcpt(&self, _address: &str) -> crate::Result<bool> {
|
||||
Err(DirectoryError::unsupported("imap", "rcpt"))
|
||||
pub async fn rcpt(&self, _address: &str) -> trc::Result<bool> {
|
||||
Err(trc::Cause::Unsupported
|
||||
.caused_by(trc::location!())
|
||||
.protocol(trc::Protocol::Imap))
|
||||
}
|
||||
|
||||
pub async fn vrfy(&self, _address: &str) -> crate::Result<Vec<String>> {
|
||||
Err(DirectoryError::unsupported("imap", "vrfy"))
|
||||
pub async fn vrfy(&self, _address: &str) -> trc::Result<Vec<String>> {
|
||||
Err(trc::Cause::Unsupported
|
||||
.caused_by(trc::location!())
|
||||
.protocol(trc::Protocol::Imap))
|
||||
}
|
||||
|
||||
pub async fn expn(&self, _address: &str) -> crate::Result<Vec<String>> {
|
||||
Err(DirectoryError::unsupported("imap", "expn"))
|
||||
pub async fn expn(&self, _address: &str) -> trc::Result<Vec<String>> {
|
||||
Err(trc::Cause::Unsupported
|
||||
.caused_by(trc::location!())
|
||||
.protocol(trc::Protocol::Imap))
|
||||
}
|
||||
|
||||
pub async fn is_local_domain(&self, domain: &str) -> crate::Result<bool> {
|
||||
pub async fn is_local_domain(&self, domain: &str) -> trc::Result<bool> {
|
||||
Ok(self.domains.contains(domain))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,13 +20,13 @@ pub trait DirectoryStore: Sync + Send {
|
||||
&self,
|
||||
by: QueryBy<'_>,
|
||||
return_member_of: bool,
|
||||
) -> crate::Result<Option<Principal<u32>>>;
|
||||
async fn email_to_ids(&self, email: &str) -> crate::Result<Vec<u32>>;
|
||||
) -> trc::Result<Option<Principal<u32>>>;
|
||||
async fn email_to_ids(&self, email: &str) -> trc::Result<Vec<u32>>;
|
||||
|
||||
async fn is_local_domain(&self, domain: &str) -> crate::Result<bool>;
|
||||
async fn rcpt(&self, address: &str) -> crate::Result<bool>;
|
||||
async fn vrfy(&self, address: &str) -> crate::Result<Vec<String>>;
|
||||
async fn expn(&self, address: &str) -> crate::Result<Vec<String>>;
|
||||
async fn is_local_domain(&self, domain: &str) -> trc::Result<bool>;
|
||||
async fn rcpt(&self, address: &str) -> trc::Result<bool>;
|
||||
async fn vrfy(&self, address: &str) -> trc::Result<Vec<String>>;
|
||||
async fn expn(&self, address: &str) -> trc::Result<Vec<String>>;
|
||||
}
|
||||
|
||||
impl DirectoryStore for Store {
|
||||
@@ -34,7 +34,7 @@ impl DirectoryStore for Store {
|
||||
&self,
|
||||
by: QueryBy<'_>,
|
||||
return_member_of: bool,
|
||||
) -> crate::Result<Option<Principal<u32>>> {
|
||||
) -> trc::Result<Option<Principal<u32>>> {
|
||||
let (account_id, secret) = match by {
|
||||
QueryBy::Name(name) => (self.get_account_id(name).await?, None),
|
||||
QueryBy::Id(account_id) => (account_id.into(), None),
|
||||
@@ -79,7 +79,7 @@ impl DirectoryStore for Store {
|
||||
}
|
||||
}
|
||||
|
||||
async fn email_to_ids(&self, email: &str) -> crate::Result<Vec<u32>> {
|
||||
async fn email_to_ids(&self, email: &str) -> trc::Result<Vec<u32>> {
|
||||
if let Some(ptype) = self
|
||||
.get_value::<PrincipalIdType>(ValueKey::from(ValueClass::Directory(
|
||||
DirectoryClass::EmailToId(email.as_bytes().to_vec()),
|
||||
@@ -89,32 +89,30 @@ impl DirectoryStore for Store {
|
||||
if ptype.typ != Type::List {
|
||||
Ok(vec![ptype.account_id])
|
||||
} else {
|
||||
self.get_members(ptype.account_id).await.map_err(Into::into)
|
||||
self.get_members(ptype.account_id).await
|
||||
}
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
async fn is_local_domain(&self, domain: &str) -> crate::Result<bool> {
|
||||
async fn is_local_domain(&self, domain: &str) -> trc::Result<bool> {
|
||||
self.get_value::<()>(ValueKey::from(ValueClass::Directory(
|
||||
DirectoryClass::Domain(domain.as_bytes().to_vec()),
|
||||
)))
|
||||
.await
|
||||
.map(|ids| ids.is_some())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn rcpt(&self, address: &str) -> crate::Result<bool> {
|
||||
async fn rcpt(&self, address: &str) -> trc::Result<bool> {
|
||||
self.get_value::<()>(ValueKey::from(ValueClass::Directory(
|
||||
DirectoryClass::EmailToId(address.as_bytes().to_vec()),
|
||||
)))
|
||||
.await
|
||||
.map(|ids| ids.is_some())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn vrfy(&self, address: &str) -> crate::Result<Vec<String>> {
|
||||
async fn vrfy(&self, address: &str) -> trc::Result<Vec<String>> {
|
||||
let mut results = Vec::new();
|
||||
let address = address.split('@').next().unwrap_or(address);
|
||||
if address.len() > 3 {
|
||||
@@ -141,7 +139,7 @@ impl DirectoryStore for Store {
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
async fn expn(&self, address: &str) -> crate::Result<Vec<String>> {
|
||||
async fn expn(&self, address: &str) -> trc::Result<Vec<String>> {
|
||||
let mut results = Vec::new();
|
||||
for account_id in self.email_to_ids(address).await? {
|
||||
if let Some(email) = self
|
||||
|
||||
@@ -12,8 +12,9 @@ use store::{
|
||||
},
|
||||
Deserialize, IterateParams, Serialize, Store, ValueKey, U32_LEN,
|
||||
};
|
||||
use trc::AddContext;
|
||||
|
||||
use crate::{DirectoryError, ManagementError, Principal, QueryBy, Type};
|
||||
use crate::{Principal, QueryBy, Type};
|
||||
|
||||
use super::{
|
||||
lookup::DirectoryStore, PrincipalAction, PrincipalField, PrincipalIdType, PrincipalUpdate,
|
||||
@@ -22,83 +23,74 @@ use super::{
|
||||
|
||||
#[allow(async_fn_in_trait)]
|
||||
pub trait ManageDirectory: Sized {
|
||||
async fn get_account_id(&self, name: &str) -> crate::Result<Option<u32>>;
|
||||
async fn get_or_create_account_id(&self, name: &str) -> crate::Result<u32>;
|
||||
async fn get_account_name(&self, account_id: u32) -> crate::Result<Option<String>>;
|
||||
async fn get_member_of(&self, account_id: u32) -> crate::Result<Vec<u32>>;
|
||||
async fn get_members(&self, account_id: u32) -> crate::Result<Vec<u32>>;
|
||||
async fn get_account_id(&self, name: &str) -> trc::Result<Option<u32>>;
|
||||
async fn get_or_create_account_id(&self, name: &str) -> trc::Result<u32>;
|
||||
async fn get_account_name(&self, account_id: u32) -> trc::Result<Option<String>>;
|
||||
async fn get_member_of(&self, account_id: u32) -> trc::Result<Vec<u32>>;
|
||||
async fn get_members(&self, account_id: u32) -> trc::Result<Vec<u32>>;
|
||||
async fn create_account(
|
||||
&self,
|
||||
principal: Principal<String>,
|
||||
members: Vec<String>,
|
||||
) -> crate::Result<u32>;
|
||||
) -> trc::Result<u32>;
|
||||
async fn update_account(
|
||||
&self,
|
||||
by: QueryBy<'_>,
|
||||
changes: Vec<PrincipalUpdate>,
|
||||
) -> crate::Result<()>;
|
||||
async fn delete_account(&self, by: QueryBy<'_>) -> crate::Result<()>;
|
||||
) -> trc::Result<()>;
|
||||
async fn delete_account(&self, by: QueryBy<'_>) -> trc::Result<()>;
|
||||
async fn list_accounts(
|
||||
&self,
|
||||
filter: Option<&str>,
|
||||
typ: Option<Type>,
|
||||
) -> crate::Result<Vec<String>>;
|
||||
async fn map_group_ids(&self, principal: Principal<u32>) -> crate::Result<Principal<String>>;
|
||||
) -> trc::Result<Vec<String>>;
|
||||
async fn map_group_ids(&self, principal: Principal<u32>) -> trc::Result<Principal<String>>;
|
||||
async fn map_principal(
|
||||
&self,
|
||||
principal: Principal<String>,
|
||||
create_if_missing: bool,
|
||||
) -> crate::Result<Principal<u32>>;
|
||||
) -> trc::Result<Principal<u32>>;
|
||||
async fn map_group_names(
|
||||
&self,
|
||||
members: Vec<String>,
|
||||
create_if_missing: bool,
|
||||
) -> crate::Result<Vec<u32>>;
|
||||
async fn create_domain(&self, domain: &str) -> crate::Result<()>;
|
||||
async fn delete_domain(&self, domain: &str) -> crate::Result<()>;
|
||||
async fn list_domains(&self, filter: Option<&str>) -> crate::Result<Vec<String>>;
|
||||
) -> trc::Result<Vec<u32>>;
|
||||
async fn create_domain(&self, domain: &str) -> trc::Result<()>;
|
||||
async fn delete_domain(&self, domain: &str) -> trc::Result<()>;
|
||||
async fn list_domains(&self, filter: Option<&str>) -> trc::Result<Vec<String>>;
|
||||
}
|
||||
|
||||
impl ManageDirectory for Store {
|
||||
async fn get_account_name(&self, account_id: u32) -> crate::Result<Option<String>> {
|
||||
async fn get_account_name(&self, account_id: u32) -> trc::Result<Option<String>> {
|
||||
self.get_value::<Principal<u32>>(ValueKey::from(ValueClass::Directory(
|
||||
DirectoryClass::Principal(account_id),
|
||||
)))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.map(|v| {
|
||||
if let Some(v) = v {
|
||||
Some(v.name)
|
||||
} else {
|
||||
tracing::debug!(
|
||||
context = "directory",
|
||||
event = "not_found",
|
||||
account = account_id,
|
||||
"Principal not found for account id"
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|v| if let Some(v) = v { Some(v.name) } else { None })
|
||||
.caused_by(trc::location!())
|
||||
}
|
||||
|
||||
async fn get_account_id(&self, name: &str) -> crate::Result<Option<u32>> {
|
||||
async fn get_account_id(&self, name: &str) -> trc::Result<Option<u32>> {
|
||||
self.get_value::<PrincipalIdType>(ValueKey::from(ValueClass::Directory(
|
||||
DirectoryClass::NameToId(name.as_bytes().to_vec()),
|
||||
)))
|
||||
.await
|
||||
.map(|v| v.map(|v| v.account_id))
|
||||
.map_err(Into::into)
|
||||
.caused_by(trc::location!())
|
||||
}
|
||||
|
||||
// Used by all directories except internal
|
||||
async fn get_or_create_account_id(&self, name: &str) -> crate::Result<u32> {
|
||||
async fn get_or_create_account_id(&self, name: &str) -> trc::Result<u32> {
|
||||
let mut try_count = 0;
|
||||
let name = name.to_lowercase();
|
||||
|
||||
loop {
|
||||
// Try to obtain ID
|
||||
if let Some(account_id) = self.get_account_id(&name).await? {
|
||||
if let Some(account_id) = self
|
||||
.get_account_id(&name)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
return Ok(account_id);
|
||||
}
|
||||
|
||||
@@ -129,16 +121,13 @@ impl ManageDirectory for Store {
|
||||
Ok(account_id) => {
|
||||
return Ok(account_id);
|
||||
}
|
||||
Err(store::Error::AssertValueFailed) if try_count < 3 => {
|
||||
try_count += 1;
|
||||
continue;
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!(event = "error",
|
||||
context = "store",
|
||||
error = ?err,
|
||||
"Failed to generate account id");
|
||||
return Err(err.into());
|
||||
if err.matches(trc::Cause::AssertValue) && try_count < 3 {
|
||||
try_count += 1;
|
||||
continue;
|
||||
} else {
|
||||
return Err(err.caused_by(trc::location!()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,41 +137,46 @@ impl ManageDirectory for Store {
|
||||
&self,
|
||||
principal: Principal<String>,
|
||||
members: Vec<String>,
|
||||
) -> crate::Result<u32> {
|
||||
) -> trc::Result<u32> {
|
||||
// Make sure the principal has a name
|
||||
if principal.name.is_empty() {
|
||||
return Err(DirectoryError::Management(ManagementError::MissingField(
|
||||
PrincipalField::Name,
|
||||
)));
|
||||
return Err(not_found(PrincipalField::Name));
|
||||
}
|
||||
|
||||
// Map group names
|
||||
let mut principal = self.map_principal(principal, false).await?;
|
||||
let members = self.map_group_names(members, false).await?;
|
||||
let mut principal = self
|
||||
.map_principal(principal, false)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
let members = self
|
||||
.map_group_names(members, false)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// Make sure new name is not taken
|
||||
principal.name = principal.name.to_lowercase();
|
||||
if self.get_account_id(&principal.name).await?.is_some() {
|
||||
return Err(DirectoryError::Management(ManagementError::AlreadyExists {
|
||||
field: PrincipalField::Name,
|
||||
value: principal.name,
|
||||
}));
|
||||
if self
|
||||
.get_account_id(&principal.name)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.is_some()
|
||||
{
|
||||
return Err(err_exists(PrincipalField::Name, principal.name));
|
||||
}
|
||||
|
||||
// Make sure the e-mail is not taken and validate domain
|
||||
for email in principal.emails.iter_mut() {
|
||||
*email = email.to_lowercase();
|
||||
if self.rcpt(email).await? {
|
||||
return Err(DirectoryError::Management(ManagementError::AlreadyExists {
|
||||
field: PrincipalField::Emails,
|
||||
value: email.to_string(),
|
||||
}));
|
||||
if self.rcpt(email).await.caused_by(trc::location!())? {
|
||||
return Err(err_exists(PrincipalField::Emails, email.to_string()));
|
||||
}
|
||||
if let Some(domain) = email.split('@').nth(1) {
|
||||
if !self.is_local_domain(domain).await? {
|
||||
return Err(DirectoryError::Management(ManagementError::NotFound(
|
||||
domain.to_string(),
|
||||
)));
|
||||
if !self
|
||||
.is_local_domain(domain)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
return Err(not_found(domain.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,14 +248,15 @@ impl ManageDirectory for Store {
|
||||
self.write(batch.build())
|
||||
.await
|
||||
.and_then(|r| r.last_document_id())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn delete_account(&self, by: QueryBy<'_>) -> crate::Result<()> {
|
||||
async fn delete_account(&self, by: QueryBy<'_>) -> trc::Result<()> {
|
||||
let account_id = match by {
|
||||
QueryBy::Name(name) => self.get_account_id(name).await?.ok_or_else(|| {
|
||||
DirectoryError::Management(ManagementError::NotFound(name.to_string()))
|
||||
})?,
|
||||
QueryBy::Name(name) => self
|
||||
.get_account_id(name)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or_else(|| not_found(name.to_string()))?,
|
||||
QueryBy::Id(account_id) => account_id,
|
||||
QueryBy::Credentials(_) => unreachable!(),
|
||||
};
|
||||
@@ -270,19 +265,24 @@ impl ManageDirectory for Store {
|
||||
.get_value::<Principal<u32>>(ValueKey::from(ValueClass::Directory(
|
||||
DirectoryClass::Principal(account_id),
|
||||
)))
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
DirectoryError::Management(ManagementError::NotFound(account_id.to_string()))
|
||||
})?;
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or_else(|| not_found(account_id.to_string()))?;
|
||||
|
||||
// Unlink all account's blobs
|
||||
self.blob_hash_unlink_account(account_id).await?;
|
||||
self.blob_hash_unlink_account(account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// Revoke ACLs
|
||||
self.acl_revoke_all(account_id).await?;
|
||||
self.acl_revoke_all(account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// Delete account data
|
||||
self.purge_account(account_id).await?;
|
||||
self.purge_account(account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// Delete account
|
||||
let mut batch = BatchBuilder::new();
|
||||
@@ -298,7 +298,11 @@ impl ManageDirectory for Store {
|
||||
batch.clear(DirectoryClass::EmailToId(email.into_bytes()));
|
||||
}
|
||||
|
||||
for member_id in self.get_member_of(account_id).await? {
|
||||
for member_id in self
|
||||
.get_member_of(account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
batch.clear(DirectoryClass::MemberOf {
|
||||
principal_id: MaybeDynamicId::Static(account_id),
|
||||
member_of: MaybeDynamicId::Static(member_id),
|
||||
@@ -309,7 +313,11 @@ impl ManageDirectory for Store {
|
||||
});
|
||||
}
|
||||
|
||||
for member_id in self.get_members(account_id).await? {
|
||||
for member_id in self
|
||||
.get_members(account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
batch.clear(DirectoryClass::MemberOf {
|
||||
principal_id: MaybeDynamicId::Static(member_id),
|
||||
member_of: MaybeDynamicId::Static(account_id),
|
||||
@@ -320,7 +328,9 @@ impl ManageDirectory for Store {
|
||||
});
|
||||
}
|
||||
|
||||
self.write(batch.build()).await?;
|
||||
self.write(batch.build())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -329,11 +339,13 @@ impl ManageDirectory for Store {
|
||||
&self,
|
||||
by: QueryBy<'_>,
|
||||
changes: Vec<PrincipalUpdate>,
|
||||
) -> crate::Result<()> {
|
||||
) -> trc::Result<()> {
|
||||
let account_id = match by {
|
||||
QueryBy::Name(name) => self.get_account_id(name).await?.ok_or_else(|| {
|
||||
DirectoryError::Management(ManagementError::NotFound(name.to_string()))
|
||||
})?,
|
||||
QueryBy::Name(name) => self
|
||||
.get_account_id(name)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or_else(|| not_found(name.to_string()))?,
|
||||
QueryBy::Id(account_id) => account_id,
|
||||
QueryBy::Credentials(_) => unreachable!(),
|
||||
};
|
||||
@@ -343,14 +355,19 @@ impl ManageDirectory for Store {
|
||||
.get_value::<HashedValue<Principal<u32>>>(ValueKey::from(ValueClass::Directory(
|
||||
DirectoryClass::Principal(account_id),
|
||||
)))
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
DirectoryError::Management(ManagementError::NotFound(account_id.to_string()))
|
||||
})?;
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or_else(|| not_found(account_id.to_string()))?;
|
||||
|
||||
// Obtain members and memberOf
|
||||
let mut member_of = self.get_member_of(account_id).await?;
|
||||
let mut members = self.get_members(account_id).await?;
|
||||
let mut member_of = self
|
||||
.get_member_of(account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
let mut members = self
|
||||
.get_members(account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// Apply changes
|
||||
let mut batch = BatchBuilder::new();
|
||||
@@ -375,13 +392,13 @@ impl ManageDirectory for Store {
|
||||
// Make sure new name is not taken
|
||||
let new_name = new_name.to_lowercase();
|
||||
if principal.inner.name != new_name {
|
||||
if self.get_account_id(&new_name).await?.is_some() {
|
||||
return Err(DirectoryError::Management(
|
||||
ManagementError::AlreadyExists {
|
||||
field: PrincipalField::Name,
|
||||
value: new_name,
|
||||
},
|
||||
));
|
||||
if self
|
||||
.get_account_id(&new_name)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.is_some()
|
||||
{
|
||||
return Err(err_exists(PrincipalField::Name, new_name));
|
||||
}
|
||||
|
||||
batch.clear(ValueClass::Directory(DirectoryClass::NameToId(
|
||||
@@ -405,7 +422,7 @@ impl ManageDirectory for Store {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return Err(DirectoryError::Unsupported);
|
||||
return Err(trc::Cause::Unsupported.caused_by(trc::location!()));
|
||||
}
|
||||
(
|
||||
PrincipalAction::Set,
|
||||
@@ -472,19 +489,16 @@ impl ManageDirectory for Store {
|
||||
.collect::<Vec<_>>();
|
||||
for email in &emails {
|
||||
if !principal.inner.emails.contains(email) {
|
||||
if self.rcpt(email).await? {
|
||||
return Err(DirectoryError::Management(
|
||||
ManagementError::AlreadyExists {
|
||||
field: PrincipalField::Emails,
|
||||
value: email.to_string(),
|
||||
},
|
||||
));
|
||||
if self.rcpt(email).await.caused_by(trc::location!())? {
|
||||
return Err(err_exists(PrincipalField::Emails, email.to_string()));
|
||||
}
|
||||
if let Some(domain) = email.split('@').nth(1) {
|
||||
if !self.is_local_domain(domain).await? {
|
||||
return Err(DirectoryError::Management(
|
||||
ManagementError::NotFound(domain.to_string()),
|
||||
));
|
||||
if !self
|
||||
.is_local_domain(domain)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
return Err(not_found(domain.to_string()));
|
||||
}
|
||||
}
|
||||
batch.set(
|
||||
@@ -513,19 +527,16 @@ impl ManageDirectory for Store {
|
||||
) => {
|
||||
let email = email.to_lowercase();
|
||||
if !principal.inner.emails.contains(&email) {
|
||||
if self.rcpt(&email).await? {
|
||||
return Err(DirectoryError::Management(
|
||||
ManagementError::AlreadyExists {
|
||||
field: PrincipalField::Emails,
|
||||
value: email,
|
||||
},
|
||||
));
|
||||
if self.rcpt(&email).await.caused_by(trc::location!())? {
|
||||
return Err(err_exists(PrincipalField::Emails, email));
|
||||
}
|
||||
if let Some(domain) = email.split('@').nth(1) {
|
||||
if !self.is_local_domain(domain).await? {
|
||||
return Err(DirectoryError::Management(ManagementError::NotFound(
|
||||
domain.to_string(),
|
||||
)));
|
||||
if !self
|
||||
.is_local_domain(domain)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
return Err(not_found(domain.to_string()));
|
||||
}
|
||||
}
|
||||
batch.set(
|
||||
@@ -559,9 +570,11 @@ impl ManageDirectory for Store {
|
||||
) => {
|
||||
let mut new_member_of = Vec::new();
|
||||
for member in members {
|
||||
let member_id = self.get_account_id(&member).await?.ok_or_else(|| {
|
||||
DirectoryError::Management(ManagementError::NotFound(member))
|
||||
})?;
|
||||
let member_id = self
|
||||
.get_account_id(&member)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or_else(|| not_found(member))?;
|
||||
if !member_of.contains(&member_id) {
|
||||
batch.set(
|
||||
ValueClass::Directory(DirectoryClass::MemberOf {
|
||||
@@ -602,9 +615,11 @@ impl ManageDirectory for Store {
|
||||
PrincipalField::MemberOf,
|
||||
PrincipalValue::String(member),
|
||||
) => {
|
||||
let member_id = self.get_account_id(&member).await?.ok_or_else(|| {
|
||||
DirectoryError::Management(ManagementError::NotFound(member))
|
||||
})?;
|
||||
let member_id = self
|
||||
.get_account_id(&member)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or_else(|| not_found(member))?;
|
||||
if !member_of.contains(&member_id) {
|
||||
batch.set(
|
||||
ValueClass::Directory(DirectoryClass::MemberOf {
|
||||
@@ -628,7 +643,11 @@ impl ManageDirectory for Store {
|
||||
PrincipalField::MemberOf,
|
||||
PrincipalValue::String(member),
|
||||
) => {
|
||||
if let Some(member_id) = self.get_account_id(&member).await? {
|
||||
if let Some(member_id) = self
|
||||
.get_account_id(&member)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
if let Some(pos) = member_of.iter().position(|v| *v == member_id) {
|
||||
batch.clear(ValueClass::Directory(DirectoryClass::MemberOf {
|
||||
principal_id: MaybeDynamicId::Static(account_id),
|
||||
@@ -650,9 +669,11 @@ impl ManageDirectory for Store {
|
||||
) => {
|
||||
let mut new_members = Vec::new();
|
||||
for member in members_ {
|
||||
let member_id = self.get_account_id(&member).await?.ok_or_else(|| {
|
||||
DirectoryError::Management(ManagementError::NotFound(member))
|
||||
})?;
|
||||
let member_id = self
|
||||
.get_account_id(&member)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or_else(|| not_found(member))?;
|
||||
if !members.contains(&member_id) {
|
||||
batch.set(
|
||||
ValueClass::Directory(DirectoryClass::MemberOf {
|
||||
@@ -693,9 +714,11 @@ impl ManageDirectory for Store {
|
||||
PrincipalField::Members,
|
||||
PrincipalValue::String(member),
|
||||
) => {
|
||||
let member_id = self.get_account_id(&member).await?.ok_or_else(|| {
|
||||
DirectoryError::Management(ManagementError::NotFound(member))
|
||||
})?;
|
||||
let member_id = self
|
||||
.get_account_id(&member)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or_else(|| not_found(member))?;
|
||||
if !members.contains(&member_id) {
|
||||
batch.set(
|
||||
ValueClass::Directory(DirectoryClass::MemberOf {
|
||||
@@ -719,7 +742,11 @@ impl ManageDirectory for Store {
|
||||
PrincipalField::Members,
|
||||
PrincipalValue::String(member),
|
||||
) => {
|
||||
if let Some(member_id) = self.get_account_id(&member).await? {
|
||||
if let Some(member_id) = self
|
||||
.get_account_id(&member)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
if let Some(pos) = members.iter().position(|v| *v == member_id) {
|
||||
batch.clear(ValueClass::Directory(DirectoryClass::MemberOf {
|
||||
principal_id: MaybeDynamicId::Static(member_id),
|
||||
@@ -735,7 +762,7 @@ impl ManageDirectory for Store {
|
||||
}
|
||||
|
||||
_ => {
|
||||
return Err(DirectoryError::Unsupported);
|
||||
return Err(trc::Cause::Unsupported.caused_by(trc::location!()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -749,45 +776,37 @@ impl ManageDirectory for Store {
|
||||
);
|
||||
}
|
||||
|
||||
self.write(batch.build()).await?;
|
||||
self.write(batch.build())
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_domain(&self, domain: &str) -> crate::Result<()> {
|
||||
async fn create_domain(&self, domain: &str) -> trc::Result<()> {
|
||||
if !domain.contains('.') {
|
||||
return Err(DirectoryError::Management(ManagementError::MissingField(
|
||||
PrincipalField::Name,
|
||||
)));
|
||||
return Err(err_missing(PrincipalField::Name));
|
||||
}
|
||||
let mut batch = BatchBuilder::new();
|
||||
batch.set(
|
||||
ValueClass::Directory(DirectoryClass::Domain(domain.to_lowercase().into_bytes())),
|
||||
vec![],
|
||||
);
|
||||
self.write(batch.build())
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.map(|_| ())
|
||||
self.write(batch.build()).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn delete_domain(&self, domain: &str) -> crate::Result<()> {
|
||||
async fn delete_domain(&self, domain: &str) -> trc::Result<()> {
|
||||
if !domain.contains('.') {
|
||||
return Err(DirectoryError::Management(ManagementError::MissingField(
|
||||
PrincipalField::Name,
|
||||
)));
|
||||
return Err(err_missing(PrincipalField::Name));
|
||||
}
|
||||
let mut batch = BatchBuilder::new();
|
||||
batch.clear(ValueClass::Directory(DirectoryClass::Domain(
|
||||
domain.to_lowercase().into_bytes(),
|
||||
)));
|
||||
self.write(batch.build())
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.map(|_| ())
|
||||
self.write(batch.build()).await.map(|_| ())
|
||||
}
|
||||
|
||||
async fn map_group_ids(&self, principal: Principal<u32>) -> crate::Result<Principal<String>> {
|
||||
async fn map_group_ids(&self, principal: Principal<u32>) -> trc::Result<Principal<String>> {
|
||||
let mut mapped = Principal {
|
||||
id: principal.id,
|
||||
typ: principal.typ,
|
||||
@@ -800,7 +819,11 @@ impl ManageDirectory for Store {
|
||||
};
|
||||
|
||||
for account_id in principal.member_of {
|
||||
if let Some(name) = self.get_account_name(account_id).await? {
|
||||
if let Some(name) = self
|
||||
.get_account_name(account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
mapped.member_of.push(name);
|
||||
}
|
||||
}
|
||||
@@ -812,7 +835,7 @@ impl ManageDirectory for Store {
|
||||
&self,
|
||||
principal: Principal<String>,
|
||||
create_if_missing: bool,
|
||||
) -> crate::Result<Principal<u32>> {
|
||||
) -> trc::Result<Principal<u32>> {
|
||||
Ok(Principal {
|
||||
id: principal.id,
|
||||
typ: principal.typ,
|
||||
@@ -822,7 +845,8 @@ impl ManageDirectory for Store {
|
||||
emails: principal.emails,
|
||||
member_of: self
|
||||
.map_group_names(principal.member_of, create_if_missing)
|
||||
.await?,
|
||||
.await
|
||||
.caused_by(trc::location!())?,
|
||||
description: principal.description,
|
||||
})
|
||||
}
|
||||
@@ -831,16 +855,19 @@ impl ManageDirectory for Store {
|
||||
&self,
|
||||
members: Vec<String>,
|
||||
create_if_missing: bool,
|
||||
) -> crate::Result<Vec<u32>> {
|
||||
) -> trc::Result<Vec<u32>> {
|
||||
let mut member_ids = Vec::with_capacity(members.len());
|
||||
|
||||
for member in members {
|
||||
let account_id = if create_if_missing {
|
||||
self.get_or_create_account_id(&member).await?
|
||||
self.get_or_create_account_id(&member)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
} else {
|
||||
self.get_account_id(&member)
|
||||
.await?
|
||||
.ok_or_else(|| DirectoryError::Management(ManagementError::NotFound(member)))?
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or_else(|| not_found(member))?
|
||||
};
|
||||
member_ids.push(account_id);
|
||||
}
|
||||
@@ -852,7 +879,7 @@ impl ManageDirectory for Store {
|
||||
&self,
|
||||
filter: Option<&str>,
|
||||
typ: Option<Type>,
|
||||
) -> crate::Result<Vec<String>> {
|
||||
) -> trc::Result<Vec<String>> {
|
||||
let from_key = ValueKey::from(ValueClass::Directory(DirectoryClass::NameToId(vec![])));
|
||||
let to_key = ValueKey::from(ValueClass::Directory(DirectoryClass::NameToId(vec![
|
||||
u8::MAX;
|
||||
@@ -863,7 +890,7 @@ impl ManageDirectory for Store {
|
||||
self.iterate(
|
||||
IterateParams::new(from_key, to_key).ascending(),
|
||||
|key, value| {
|
||||
let pt = PrincipalIdType::deserialize(value)?;
|
||||
let pt = PrincipalIdType::deserialize(value).caused_by(trc::location!())?;
|
||||
|
||||
if typ.map_or(true, |t| pt.typ == t) {
|
||||
results.push((
|
||||
@@ -875,7 +902,8 @@ impl ManageDirectory for Store {
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
if let Some(filter) = filter {
|
||||
let mut filtered = Vec::new();
|
||||
@@ -889,12 +917,9 @@ impl ManageDirectory for Store {
|
||||
.get_value::<Principal<u32>>(ValueKey::from(ValueClass::Directory(
|
||||
DirectoryClass::Principal(account_id),
|
||||
)))
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
DirectoryError::Management(ManagementError::NotFound(
|
||||
account_id.to_string(),
|
||||
))
|
||||
})?;
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.ok_or_else(|| not_found(account_id.to_string()))?;
|
||||
if filters.iter().all(|f| {
|
||||
principal.name.to_lowercase().contains(f)
|
||||
|| principal
|
||||
@@ -916,7 +941,7 @@ impl ManageDirectory for Store {
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_domains(&self, filter: Option<&str>) -> crate::Result<Vec<String>> {
|
||||
async fn list_domains(&self, filter: Option<&str>) -> trc::Result<Vec<String>> {
|
||||
let from_key = ValueKey::from(ValueClass::Directory(DirectoryClass::Domain(vec![])));
|
||||
let to_key = ValueKey::from(ValueClass::Directory(DirectoryClass::Domain(vec![
|
||||
u8::MAX;
|
||||
@@ -934,12 +959,13 @@ impl ManageDirectory for Store {
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
async fn get_member_of(&self, account_id: u32) -> crate::Result<Vec<u32>> {
|
||||
async fn get_member_of(&self, account_id: u32) -> trc::Result<Vec<u32>> {
|
||||
let from_key = ValueKey::from(ValueClass::Directory(DirectoryClass::MemberOf {
|
||||
principal_id: account_id,
|
||||
member_of: 0,
|
||||
@@ -956,11 +982,12 @@ impl ManageDirectory for Store {
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
async fn get_members(&self, account_id: u32) -> crate::Result<Vec<u32>> {
|
||||
async fn get_members(&self, account_id: u32) -> trc::Result<Vec<u32>> {
|
||||
let from_key = ValueKey::from(ValueClass::Directory(DirectoryClass::Members {
|
||||
principal_id: account_id,
|
||||
has_member: 0,
|
||||
@@ -977,15 +1004,16 @@ impl ManageDirectory for Store {
|
||||
Ok(true)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
Ok(results)
|
||||
}
|
||||
}
|
||||
|
||||
impl SerializeWithId for Principal<u32> {
|
||||
fn serialize_with_id(&self, ids: &AssignedIds) -> store::Result<Vec<u8>> {
|
||||
fn serialize_with_id(&self, ids: &AssignedIds) -> trc::Result<Vec<u8>> {
|
||||
let mut principal = self.clone();
|
||||
principal.id = ids.last_document_id()?;
|
||||
principal.id = ids.last_document_id().caused_by(trc::location!())?;
|
||||
Ok(principal.serialize())
|
||||
}
|
||||
}
|
||||
@@ -1000,7 +1028,7 @@ impl From<Principal<u32>> for MaybeDynamicValue {
|
||||
struct DynamicPrincipalIdType(Type);
|
||||
|
||||
impl SerializeWithId for DynamicPrincipalIdType {
|
||||
fn serialize_with_id(&self, ids: &AssignedIds) -> store::Result<Vec<u8>> {
|
||||
fn serialize_with_id(&self, ids: &AssignedIds) -> trc::Result<Vec<u8>> {
|
||||
ids.last_document_id()
|
||||
.map(|account_id| PrincipalIdType::new(account_id, self.0).serialize())
|
||||
}
|
||||
@@ -1026,3 +1054,23 @@ impl From<Principal<String>> for Principal<u32> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn err_missing(field: impl Into<trc::Value>) -> trc::Error {
|
||||
trc::Cause::MissingParameter.ctx(trc::Key::Key, field)
|
||||
}
|
||||
|
||||
fn err_exists(field: impl Into<trc::Value>, value: impl Into<trc::Value>) -> trc::Error {
|
||||
trc::Cause::AlreadyExists
|
||||
.ctx(trc::Key::Key, field)
|
||||
.ctx(trc::Key::Value, value)
|
||||
}
|
||||
|
||||
fn not_found(value: impl Into<trc::Value>) -> trc::Error {
|
||||
trc::Cause::NotFound.ctx(trc::Key::Key, value)
|
||||
}
|
||||
|
||||
impl From<PrincipalField> for trc::Value {
|
||||
fn from(value: PrincipalField) -> Self {
|
||||
trc::Value::Static(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,9 +56,12 @@ impl Serialize for &Principal<u32> {
|
||||
}
|
||||
|
||||
impl Deserialize for Principal<u32> {
|
||||
fn deserialize(bytes: &[u8]) -> store::Result<Self> {
|
||||
deserialize(bytes)
|
||||
.ok_or_else(|| store::Error::InternalError("Failed to deserialize principal".into()))
|
||||
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
|
||||
deserialize(bytes).ok_or_else(|| {
|
||||
trc::Cause::DataCorruption
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,14 +75,18 @@ impl Serialize for PrincipalIdType {
|
||||
}
|
||||
|
||||
impl Deserialize for PrincipalIdType {
|
||||
fn deserialize(bytes: &[u8]) -> store::Result<Self> {
|
||||
let mut bytes = bytes.iter();
|
||||
fn deserialize(bytes_: &[u8]) -> trc::Result<Self> {
|
||||
let mut bytes = bytes_.iter();
|
||||
Ok(PrincipalIdType {
|
||||
account_id: bytes.next_leb128().ok_or_else(|| {
|
||||
store::Error::InternalError("Failed to deserialize principal account id".into())
|
||||
trc::Cause::DataCorruption
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes_)
|
||||
})?,
|
||||
typ: Type::from_u8(*bytes.next().ok_or_else(|| {
|
||||
store::Error::InternalError("Failed to deserialize principal id type".into())
|
||||
trc::Cause::DataCorruption
|
||||
.caused_by(trc::location!())
|
||||
.ctx(trc::Key::Value, bytes_)
|
||||
})?),
|
||||
})
|
||||
}
|
||||
@@ -189,15 +196,21 @@ impl PrincipalUpdate {
|
||||
|
||||
impl Display for PrincipalField {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.as_str().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl PrincipalField {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
PrincipalField::Name => write!(f, "name"),
|
||||
PrincipalField::Type => write!(f, "type"),
|
||||
PrincipalField::Quota => write!(f, "quota"),
|
||||
PrincipalField::Description => write!(f, "description"),
|
||||
PrincipalField::Secrets => write!(f, "secrets"),
|
||||
PrincipalField::Emails => write!(f, "emails"),
|
||||
PrincipalField::MemberOf => write!(f, "memberOf"),
|
||||
PrincipalField::Members => write!(f, "members"),
|
||||
PrincipalField::Name => "name",
|
||||
PrincipalField::Type => "type",
|
||||
PrincipalField::Quota => "quota",
|
||||
PrincipalField::Description => "description",
|
||||
PrincipalField::Secrets => "secrets",
|
||||
PrincipalField::Emails => "emails",
|
||||
PrincipalField::MemberOf => "memberOf",
|
||||
PrincipalField::Members => "members",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use ldap3::{Ldap, LdapConnAsync, LdapError, Scope, SearchEntry};
|
||||
use ldap3::{Ldap, LdapConnAsync, Scope, SearchEntry};
|
||||
use mail_send::Credentials;
|
||||
|
||||
use crate::{backend::internal::manage::ManageDirectory, DirectoryError, Principal, QueryBy, Type};
|
||||
use crate::{backend::internal::manage::ManageDirectory, IntoError, Principal, QueryBy, Type};
|
||||
|
||||
use super::{LdapDirectory, LdapMappings};
|
||||
|
||||
@@ -16,8 +16,8 @@ impl LdapDirectory {
|
||||
&self,
|
||||
by: QueryBy<'_>,
|
||||
return_member_of: bool,
|
||||
) -> crate::Result<Option<Principal<u32>>> {
|
||||
let mut conn = self.pool.get().await?;
|
||||
) -> trc::Result<Option<Principal<u32>>> {
|
||||
let mut conn = self.pool.get().await.map_err(|err| err.into_error())?;
|
||||
let mut account_id = None;
|
||||
let account_name;
|
||||
|
||||
@@ -64,19 +64,26 @@ impl LdapDirectory {
|
||||
self.pool.manager().settings.clone(),
|
||||
&self.pool.manager().address,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?;
|
||||
|
||||
ldap3::drive!(conn);
|
||||
|
||||
ldap.simple_bind(&auth_bind.build(username), secret).await?;
|
||||
ldap.simple_bind(&auth_bind.build(username), secret)
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?;
|
||||
|
||||
match self
|
||||
.find_principal(&mut ldap, &self.mappings.filter_name.build(username))
|
||||
.await
|
||||
{
|
||||
Ok(Some(principal)) => principal,
|
||||
Err(DirectoryError::Ldap(LdapError::LdapResult { result }))
|
||||
if [49, 50].contains(&result.rc) =>
|
||||
Err(err)
|
||||
if err.matches(trc::Cause::Ldap)
|
||||
&& err
|
||||
.value(trc::Key::Code)
|
||||
.and_then(|v| v.to_uint())
|
||||
.map_or(false, |rc| [49, 50].contains(&rc)) =>
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -90,13 +97,6 @@ impl LdapDirectory {
|
||||
if principal.verify_secret(secret).await? {
|
||||
principal
|
||||
} else {
|
||||
tracing::debug!(
|
||||
context = "directory",
|
||||
event = "invalid_password",
|
||||
protocol = "ldap",
|
||||
account = username,
|
||||
"Invalid password for account"
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
} else {
|
||||
@@ -128,8 +128,10 @@ impl LdapDirectory {
|
||||
"objectClass=*",
|
||||
&self.mappings.attr_name,
|
||||
)
|
||||
.await?
|
||||
.success()?;
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?
|
||||
.success()
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?;
|
||||
for entry in rs {
|
||||
'outer: for (attr, value) in SearchEntry::construct(entry).attrs {
|
||||
if self.mappings.attr_name.contains(&attr) {
|
||||
@@ -156,20 +158,23 @@ impl LdapDirectory {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn email_to_ids(&self, address: &str) -> crate::Result<Vec<u32>> {
|
||||
pub async fn email_to_ids(&self, address: &str) -> trc::Result<Vec<u32>> {
|
||||
let rs = self
|
||||
.pool
|
||||
.get()
|
||||
.await?
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?
|
||||
.search(
|
||||
&self.mappings.base_dn,
|
||||
Scope::Subtree,
|
||||
&self.mappings.filter_email.build(address.as_ref()),
|
||||
&self.mappings.attr_name,
|
||||
)
|
||||
.await?
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?
|
||||
.success()
|
||||
.map(|(rs, _res)| rs)?;
|
||||
.map(|(rs, _res)| rs)
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?;
|
||||
|
||||
let mut ids = Vec::with_capacity(rs.len());
|
||||
for entry in rs {
|
||||
@@ -187,38 +192,46 @@ impl LdapDirectory {
|
||||
Ok(ids)
|
||||
}
|
||||
|
||||
pub async fn rcpt(&self, address: &str) -> crate::Result<bool> {
|
||||
pub async fn rcpt(&self, address: &str) -> trc::Result<bool> {
|
||||
self.pool
|
||||
.get()
|
||||
.await?
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?
|
||||
.streaming_search(
|
||||
&self.mappings.base_dn,
|
||||
Scope::Subtree,
|
||||
&self.mappings.filter_email.build(address.as_ref()),
|
||||
&self.mappings.attr_email_address,
|
||||
)
|
||||
.await?
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?
|
||||
.next()
|
||||
.await
|
||||
.map(|entry| entry.is_some())
|
||||
.map_err(|e| e.into())
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))
|
||||
}
|
||||
|
||||
pub async fn vrfy(&self, address: &str) -> crate::Result<Vec<String>> {
|
||||
pub async fn vrfy(&self, address: &str) -> trc::Result<Vec<String>> {
|
||||
let mut stream = self
|
||||
.pool
|
||||
.get()
|
||||
.await?
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?
|
||||
.streaming_search(
|
||||
&self.mappings.base_dn,
|
||||
Scope::Subtree,
|
||||
&self.mappings.filter_verify.build(address),
|
||||
&self.mappings.attr_email_address,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?;
|
||||
|
||||
let mut emails = Vec::new();
|
||||
while let Some(entry) = stream.next().await? {
|
||||
while let Some(entry) = stream
|
||||
.next()
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?
|
||||
{
|
||||
let entry = SearchEntry::construct(entry);
|
||||
for attr in &self.mappings.attr_email_address {
|
||||
if let Some(values) = entry.attrs.get(attr) {
|
||||
@@ -234,21 +247,27 @@ impl LdapDirectory {
|
||||
Ok(emails)
|
||||
}
|
||||
|
||||
pub async fn expn(&self, address: &str) -> crate::Result<Vec<String>> {
|
||||
pub async fn expn(&self, address: &str) -> trc::Result<Vec<String>> {
|
||||
let mut stream = self
|
||||
.pool
|
||||
.get()
|
||||
.await?
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?
|
||||
.streaming_search(
|
||||
&self.mappings.base_dn,
|
||||
Scope::Subtree,
|
||||
&self.mappings.filter_expand.build(address),
|
||||
&self.mappings.attr_email_address,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?;
|
||||
|
||||
let mut emails = Vec::new();
|
||||
while let Some(entry) = stream.next().await? {
|
||||
while let Some(entry) = stream
|
||||
.next()
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?
|
||||
{
|
||||
let entry = SearchEntry::construct(entry);
|
||||
for attr in &self.mappings.attr_email_address {
|
||||
if let Some(values) = entry.attrs.get(attr) {
|
||||
@@ -264,21 +283,23 @@ impl LdapDirectory {
|
||||
Ok(emails)
|
||||
}
|
||||
|
||||
pub async fn is_local_domain(&self, domain: &str) -> crate::Result<bool> {
|
||||
pub async fn is_local_domain(&self, domain: &str) -> trc::Result<bool> {
|
||||
self.pool
|
||||
.get()
|
||||
.await?
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?
|
||||
.streaming_search(
|
||||
&self.mappings.base_dn,
|
||||
Scope::Subtree,
|
||||
&self.mappings.filter_domains.build(domain),
|
||||
Vec::<String>::new(),
|
||||
)
|
||||
.await?
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?
|
||||
.next()
|
||||
.await
|
||||
.map(|entry| entry.is_some())
|
||||
.map_err(|e| e.into())
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,14 +308,15 @@ impl LdapDirectory {
|
||||
&self,
|
||||
conn: &mut Ldap,
|
||||
filter: &str,
|
||||
) -> crate::Result<Option<Principal<String>>> {
|
||||
) -> trc::Result<Option<Principal<String>>> {
|
||||
conn.search(
|
||||
&self.mappings.base_dn,
|
||||
Scope::Subtree,
|
||||
filter,
|
||||
&self.mappings.attrs_principal,
|
||||
)
|
||||
.await?
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?
|
||||
.success()
|
||||
.map(|(rs, _)| {
|
||||
rs.into_iter().next().map(|entry| {
|
||||
@@ -302,7 +324,7 @@ impl LdapDirectory {
|
||||
.entry_to_principal(SearchEntry::construct(entry))
|
||||
})
|
||||
})
|
||||
.map_err(Into::into)
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,12 +332,7 @@ impl LdapMappings {
|
||||
fn entry_to_principal(&self, entry: SearchEntry) -> Principal<String> {
|
||||
let mut principal = Principal::default();
|
||||
|
||||
tracing::debug!(
|
||||
context = "ldap",
|
||||
event = "fetch_principal",
|
||||
entry = ?entry,
|
||||
"LDAP entry"
|
||||
);
|
||||
trc::trace!(LdapQuery, Value = format!("{entry:?}"));
|
||||
|
||||
for (attr, value) in entry.attrs {
|
||||
if self.attr_name.contains(&attr) {
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::{Principal, QueryBy};
|
||||
use super::{EmailType, MemoryDirectory};
|
||||
|
||||
impl MemoryDirectory {
|
||||
pub async fn query(&self, by: QueryBy<'_>) -> crate::Result<Option<Principal<u32>>> {
|
||||
pub async fn query(&self, by: QueryBy<'_>) -> trc::Result<Option<Principal<u32>>> {
|
||||
match by {
|
||||
QueryBy::Name(name) => {
|
||||
for principal in &self.principals {
|
||||
@@ -48,7 +48,7 @@ impl MemoryDirectory {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub async fn email_to_ids(&self, address: &str) -> crate::Result<Vec<u32>> {
|
||||
pub async fn email_to_ids(&self, address: &str) -> trc::Result<Vec<u32>> {
|
||||
Ok(self
|
||||
.emails_to_ids
|
||||
.get(address)
|
||||
@@ -65,11 +65,11 @@ impl MemoryDirectory {
|
||||
.unwrap_or_default())
|
||||
}
|
||||
|
||||
pub async fn rcpt(&self, address: &str) -> crate::Result<bool> {
|
||||
pub async fn rcpt(&self, address: &str) -> trc::Result<bool> {
|
||||
Ok(self.emails_to_ids.contains_key(address))
|
||||
}
|
||||
|
||||
pub async fn vrfy(&self, address: &str) -> crate::Result<Vec<String>> {
|
||||
pub async fn vrfy(&self, address: &str) -> trc::Result<Vec<String>> {
|
||||
let mut result = Vec::new();
|
||||
for (key, value) in &self.emails_to_ids {
|
||||
if key.contains(address) && value.iter().any(|t| matches!(t, EmailType::Primary(_))) {
|
||||
@@ -79,7 +79,7 @@ impl MemoryDirectory {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn expn(&self, address: &str) -> crate::Result<Vec<String>> {
|
||||
pub async fn expn(&self, address: &str) -> trc::Result<Vec<String>> {
|
||||
let mut result = Vec::new();
|
||||
for (key, value) in &self.emails_to_ids {
|
||||
if key == address {
|
||||
@@ -100,7 +100,7 @@ impl MemoryDirectory {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn is_local_domain(&self, domain: &str) -> crate::Result<bool> {
|
||||
pub async fn is_local_domain(&self, domain: &str) -> trc::Result<bool> {
|
||||
Ok(self.domains.contains(domain))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,36 +7,52 @@
|
||||
use mail_send::{smtp::AssertReply, Credentials};
|
||||
use smtp_proto::Severity;
|
||||
|
||||
use crate::{DirectoryError, Principal, QueryBy};
|
||||
use crate::{IntoError, Principal, QueryBy};
|
||||
|
||||
use super::{SmtpClient, SmtpDirectory};
|
||||
|
||||
impl SmtpDirectory {
|
||||
pub async fn query(&self, query: QueryBy<'_>) -> crate::Result<Option<Principal<u32>>> {
|
||||
pub async fn query(&self, query: QueryBy<'_>) -> trc::Result<Option<Principal<u32>>> {
|
||||
if let QueryBy::Credentials(credentials) = query {
|
||||
self.pool.get().await?.authenticate(credentials).await
|
||||
self.pool
|
||||
.get()
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?
|
||||
.authenticate(credentials)
|
||||
.await
|
||||
} else {
|
||||
Err(DirectoryError::unsupported("smtp", "query"))
|
||||
Err(trc::Cause::Unsupported
|
||||
.caused_by(trc::location!())
|
||||
.protocol(trc::Protocol::Smtp))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn email_to_ids(&self, _address: &str) -> crate::Result<Vec<u32>> {
|
||||
Err(DirectoryError::unsupported("smtp", "email_to_ids"))
|
||||
pub async fn email_to_ids(&self, _address: &str) -> trc::Result<Vec<u32>> {
|
||||
Err(trc::Cause::Unsupported
|
||||
.caused_by(trc::location!())
|
||||
.protocol(trc::Protocol::Smtp))
|
||||
}
|
||||
|
||||
pub async fn rcpt(&self, address: &str) -> crate::Result<bool> {
|
||||
let mut conn = self.pool.get().await?;
|
||||
pub async fn rcpt(&self, address: &str) -> trc::Result<bool> {
|
||||
let mut conn = self
|
||||
.pool
|
||||
.get()
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?;
|
||||
if !conn.sent_mail_from {
|
||||
conn.client
|
||||
.cmd(b"MAIL FROM:<>\r\n")
|
||||
.await?
|
||||
.assert_positive_completion()?;
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?
|
||||
.assert_positive_completion()
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?;
|
||||
conn.sent_mail_from = true;
|
||||
}
|
||||
let reply = conn
|
||||
.client
|
||||
.cmd(format!("RCPT TO:<{address}>\r\n").as_bytes())
|
||||
.await?;
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?;
|
||||
match reply.severity() {
|
||||
Severity::PositiveCompletion => {
|
||||
conn.num_rcpts += 1;
|
||||
@@ -48,27 +64,32 @@ impl SmtpDirectory {
|
||||
Ok(true)
|
||||
}
|
||||
Severity::PermanentNegativeCompletion => Ok(false),
|
||||
_ => Err(mail_send::Error::UnexpectedReply(reply).into()),
|
||||
_ => Err(trc::Cause::Unexpected
|
||||
.ctx(trc::Key::Protocol, trc::Protocol::Smtp)
|
||||
.ctx(trc::Key::Code, reply.code())
|
||||
.ctx(trc::Key::Details, reply.message)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn vrfy(&self, address: &str) -> crate::Result<Vec<String>> {
|
||||
pub async fn vrfy(&self, address: &str) -> trc::Result<Vec<String>> {
|
||||
self.pool
|
||||
.get()
|
||||
.await?
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?
|
||||
.expand(&format!("VRFY {address}\r\n"))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn expn(&self, address: &str) -> crate::Result<Vec<String>> {
|
||||
pub async fn expn(&self, address: &str) -> trc::Result<Vec<String>> {
|
||||
self.pool
|
||||
.get()
|
||||
.await?
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?
|
||||
.expand(&format!("EXPN {address}\r\n"))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn is_local_domain(&self, domain: &str) -> crate::Result<bool> {
|
||||
pub async fn is_local_domain(&self, domain: &str) -> trc::Result<bool> {
|
||||
Ok(self.domains.contains(domain))
|
||||
}
|
||||
}
|
||||
@@ -77,7 +98,7 @@ impl SmtpClient {
|
||||
async fn authenticate(
|
||||
&mut self,
|
||||
credentials: &Credentials<String>,
|
||||
) -> crate::Result<Option<Principal<u32>>> {
|
||||
) -> trc::Result<Option<Principal<u32>>> {
|
||||
match self
|
||||
.client
|
||||
.authenticate(credentials, &self.capabilities)
|
||||
@@ -89,21 +110,30 @@ impl SmtpClient {
|
||||
self.num_auth_failures += 1;
|
||||
Ok(None)
|
||||
}
|
||||
_ => Err(err.into()),
|
||||
_ => Err(err.into_error()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn expand(&mut self, command: &str) -> crate::Result<Vec<String>> {
|
||||
let reply = self.client.cmd(command.as_bytes()).await?;
|
||||
async fn expand(&mut self, command: &str) -> trc::Result<Vec<String>> {
|
||||
let reply = self
|
||||
.client
|
||||
.cmd(command.as_bytes())
|
||||
.await
|
||||
.map_err(|err| err.into_error().caused_by(trc::location!()))?;
|
||||
match reply.code() {
|
||||
250 | 251 => Ok(reply
|
||||
.message()
|
||||
.split('\n')
|
||||
.map(|p| p.to_string())
|
||||
.collect::<Vec<String>>()),
|
||||
550 | 551 | 553 | 500 | 502 => Err(DirectoryError::Unsupported),
|
||||
_ => Err(mail_send::Error::UnexpectedReply(reply).into()),
|
||||
code @ (550 | 551 | 553 | 500 | 502) => Err(trc::Cause::Unsupported
|
||||
.ctx(trc::Key::Protocol, trc::Protocol::Smtp)
|
||||
.ctx(trc::Key::Code, code)),
|
||||
code => Err(trc::Cause::Unexpected
|
||||
.ctx(trc::Key::Protocol, trc::Protocol::Smtp)
|
||||
.ctx(trc::Key::Code, code)
|
||||
.ctx(trc::Key::Details, reply.message)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
use mail_send::Credentials;
|
||||
use store::{NamedRows, Rows, Value};
|
||||
use trc::AddContext;
|
||||
|
||||
use crate::{backend::internal::manage::ManageDirectory, Principal, QueryBy, Type};
|
||||
|
||||
@@ -16,7 +17,7 @@ impl SqlDirectory {
|
||||
&self,
|
||||
by: QueryBy<'_>,
|
||||
return_member_of: bool,
|
||||
) -> crate::Result<Option<Principal<u32>>> {
|
||||
) -> trc::Result<Option<Principal<u32>>> {
|
||||
let mut account_id = None;
|
||||
let account_name;
|
||||
let mut secret = None;
|
||||
@@ -27,10 +28,16 @@ impl SqlDirectory {
|
||||
|
||||
self.store
|
||||
.query::<NamedRows>(&self.mappings.query_name, vec![username.into()])
|
||||
.await?
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
}
|
||||
QueryBy::Id(uid) => {
|
||||
if let Some(username) = self.data_store.get_account_name(uid).await? {
|
||||
if let Some(username) = self
|
||||
.data_store
|
||||
.get_account_name(uid)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
account_name = username;
|
||||
} else {
|
||||
return Ok(None);
|
||||
@@ -42,7 +49,8 @@ impl SqlDirectory {
|
||||
&self.mappings.query_name,
|
||||
vec![account_name.clone().into()],
|
||||
)
|
||||
.await?
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
}
|
||||
QueryBy::Credentials(credentials) => {
|
||||
let (username, secret_) = match credentials {
|
||||
@@ -55,7 +63,8 @@ impl SqlDirectory {
|
||||
|
||||
self.store
|
||||
.query::<NamedRows>(&self.mappings.query_name, vec![username.into()])
|
||||
.await?
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
}
|
||||
};
|
||||
|
||||
@@ -64,18 +73,18 @@ impl SqlDirectory {
|
||||
}
|
||||
|
||||
// Map row to principal
|
||||
let mut principal = self.mappings.row_to_principal(result)?;
|
||||
let mut principal = self
|
||||
.mappings
|
||||
.row_to_principal(result)
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
// Validate password
|
||||
if let Some(secret) = secret {
|
||||
if !principal.verify_secret(secret).await? {
|
||||
tracing::debug!(
|
||||
context = "directory",
|
||||
event = "invalid_password",
|
||||
protocol = "sql",
|
||||
account = account_name,
|
||||
"Invalid password for account"
|
||||
);
|
||||
if !principal
|
||||
.verify_secret(secret)
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
@@ -87,7 +96,8 @@ impl SqlDirectory {
|
||||
principal.id = self
|
||||
.data_store
|
||||
.get_or_create_account_id(&account_name)
|
||||
.await?;
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
}
|
||||
principal.name = account_name;
|
||||
|
||||
@@ -99,13 +109,17 @@ impl SqlDirectory {
|
||||
&self.mappings.query_members,
|
||||
vec![principal.name.clone().into()],
|
||||
)
|
||||
.await?
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.rows
|
||||
{
|
||||
if let Some(Value::Text(account_id)) = row.values.first() {
|
||||
principal
|
||||
.member_of
|
||||
.push(self.data_store.get_or_create_account_id(account_id).await?);
|
||||
principal.member_of.push(
|
||||
self.data_store
|
||||
.get_or_create_account_id(account_id)
|
||||
.await
|
||||
.caused_by(trc::location!())?,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,31 +132,38 @@ impl SqlDirectory {
|
||||
&self.mappings.query_emails,
|
||||
vec![principal.name.clone().into()],
|
||||
)
|
||||
.await?
|
||||
.await
|
||||
.caused_by(trc::location!())?
|
||||
.into();
|
||||
}
|
||||
|
||||
Ok(Some(principal))
|
||||
}
|
||||
|
||||
pub async fn email_to_ids(&self, address: &str) -> crate::Result<Vec<u32>> {
|
||||
pub async fn email_to_ids(&self, address: &str) -> trc::Result<Vec<u32>> {
|
||||
let names = self
|
||||
.store
|
||||
.query::<Rows>(&self.mappings.query_recipients, vec![address.into()])
|
||||
.await?;
|
||||
.await
|
||||
.caused_by(trc::location!())?;
|
||||
|
||||
let mut ids = Vec::with_capacity(names.rows.len());
|
||||
|
||||
for row in names.rows {
|
||||
if let Some(Value::Text(name)) = row.values.first() {
|
||||
ids.push(self.data_store.get_or_create_account_id(name).await?);
|
||||
ids.push(
|
||||
self.data_store
|
||||
.get_or_create_account_id(name)
|
||||
.await
|
||||
.caused_by(trc::location!())?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ids)
|
||||
}
|
||||
|
||||
pub async fn rcpt(&self, address: &str) -> crate::Result<bool> {
|
||||
pub async fn rcpt(&self, address: &str) -> trc::Result<bool> {
|
||||
self.store
|
||||
.query::<bool>(
|
||||
&self.mappings.query_recipients,
|
||||
@@ -152,7 +173,7 @@ impl SqlDirectory {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub async fn vrfy(&self, address: &str) -> crate::Result<Vec<String>> {
|
||||
pub async fn vrfy(&self, address: &str) -> trc::Result<Vec<String>> {
|
||||
self.store
|
||||
.query::<Rows>(
|
||||
&self.mappings.query_verify,
|
||||
@@ -163,7 +184,7 @@ impl SqlDirectory {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub async fn expn(&self, address: &str) -> crate::Result<Vec<String>> {
|
||||
pub async fn expn(&self, address: &str) -> trc::Result<Vec<String>> {
|
||||
self.store
|
||||
.query::<Rows>(
|
||||
&self.mappings.query_expand,
|
||||
@@ -174,7 +195,7 @@ impl SqlDirectory {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub async fn is_local_domain(&self, domain: &str) -> crate::Result<bool> {
|
||||
pub async fn is_local_domain(&self, domain: &str) -> trc::Result<bool> {
|
||||
self.store
|
||||
.query::<bool>(&self.mappings.query_domains, vec![domain.into()])
|
||||
.await
|
||||
@@ -183,7 +204,7 @@ impl SqlDirectory {
|
||||
}
|
||||
|
||||
impl SqlMappings {
|
||||
pub fn row_to_principal(&self, rows: NamedRows) -> crate::Result<Principal<u32>> {
|
||||
pub fn row_to_principal(&self, rows: NamedRows) -> trc::Result<Principal<u32>> {
|
||||
let mut principal = Principal::default();
|
||||
|
||||
if let Some(row) = rows.rows.into_iter().next() {
|
||||
|
||||
@@ -41,7 +41,6 @@ impl Directories {
|
||||
.property_or_default::<bool>(("directory", id, "disable"), "false")
|
||||
.unwrap_or(false)
|
||||
{
|
||||
tracing::debug!("Skipping disabled directory {id:?}.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -104,7 +103,7 @@ pub(crate) fn build_pool<M: Manager>(
|
||||
config: &mut Config,
|
||||
prefix: &str,
|
||||
manager: M,
|
||||
) -> utils::config::Result<Pool<M>> {
|
||||
) -> Result<Pool<M>, String> {
|
||||
Pool::builder(manager)
|
||||
.runtime(Runtime::Tokio1)
|
||||
.max_size(
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use trc::AddContext;
|
||||
|
||||
use crate::{
|
||||
backend::internal::lookup::DirectoryStore, Directory, DirectoryInner, Principal, QueryBy,
|
||||
};
|
||||
@@ -13,7 +15,7 @@ impl Directory {
|
||||
&self,
|
||||
by: QueryBy<'_>,
|
||||
return_member_of: bool,
|
||||
) -> crate::Result<Option<Principal<u32>>> {
|
||||
) -> trc::Result<Option<Principal<u32>>> {
|
||||
match &self.store {
|
||||
DirectoryInner::Internal(store) => store.query(by, return_member_of).await,
|
||||
DirectoryInner::Ldap(store) => store.query(by, return_member_of).await,
|
||||
@@ -22,9 +24,10 @@ impl Directory {
|
||||
DirectoryInner::Smtp(store) => store.query(by).await,
|
||||
DirectoryInner::Memory(store) => store.query(by).await,
|
||||
}
|
||||
.caused_by( trc::location!())
|
||||
}
|
||||
|
||||
pub async fn email_to_ids(&self, email: &str) -> crate::Result<Vec<u32>> {
|
||||
pub async fn email_to_ids(&self, email: &str) -> trc::Result<Vec<u32>> {
|
||||
match &self.store {
|
||||
DirectoryInner::Internal(store) => store.email_to_ids(email).await,
|
||||
DirectoryInner::Ldap(store) => store.email_to_ids(email).await,
|
||||
@@ -33,9 +36,10 @@ impl Directory {
|
||||
DirectoryInner::Smtp(store) => store.email_to_ids(email).await,
|
||||
DirectoryInner::Memory(store) => store.email_to_ids(email).await,
|
||||
}
|
||||
.caused_by( trc::location!())
|
||||
}
|
||||
|
||||
pub async fn is_local_domain(&self, domain: &str) -> crate::Result<bool> {
|
||||
pub async fn is_local_domain(&self, domain: &str) -> trc::Result<bool> {
|
||||
// Check cache
|
||||
if let Some(cache) = &self.cache {
|
||||
if let Some(result) = cache.get_domain(domain) {
|
||||
@@ -50,7 +54,8 @@ impl Directory {
|
||||
DirectoryInner::Imap(store) => store.is_local_domain(domain).await,
|
||||
DirectoryInner::Smtp(store) => store.is_local_domain(domain).await,
|
||||
DirectoryInner::Memory(store) => store.is_local_domain(domain).await,
|
||||
}?;
|
||||
}
|
||||
.caused_by( trc::location!())?;
|
||||
|
||||
// Update cache
|
||||
if let Some(cache) = &self.cache {
|
||||
@@ -60,7 +65,7 @@ impl Directory {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn rcpt(&self, email: &str) -> crate::Result<bool> {
|
||||
pub async fn rcpt(&self, email: &str) -> trc::Result<bool> {
|
||||
// Check cache
|
||||
if let Some(cache) = &self.cache {
|
||||
if let Some(result) = cache.get_rcpt(email) {
|
||||
@@ -75,7 +80,8 @@ impl Directory {
|
||||
DirectoryInner::Imap(store) => store.rcpt(email).await,
|
||||
DirectoryInner::Smtp(store) => store.rcpt(email).await,
|
||||
DirectoryInner::Memory(store) => store.rcpt(email).await,
|
||||
}?;
|
||||
}
|
||||
.caused_by( trc::location!())?;
|
||||
|
||||
// Update cache
|
||||
if let Some(cache) = &self.cache {
|
||||
@@ -85,7 +91,7 @@ impl Directory {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn vrfy(&self, address: &str) -> crate::Result<Vec<String>> {
|
||||
pub async fn vrfy(&self, address: &str) -> trc::Result<Vec<String>> {
|
||||
match &self.store {
|
||||
DirectoryInner::Internal(store) => store.vrfy(address).await,
|
||||
DirectoryInner::Ldap(store) => store.vrfy(address).await,
|
||||
@@ -94,9 +100,10 @@ impl Directory {
|
||||
DirectoryInner::Smtp(store) => store.vrfy(address).await,
|
||||
DirectoryInner::Memory(store) => store.vrfy(address).await,
|
||||
}
|
||||
.caused_by( trc::location!())
|
||||
}
|
||||
|
||||
pub async fn expn(&self, address: &str) -> crate::Result<Vec<String>> {
|
||||
pub async fn expn(&self, address: &str) -> trc::Result<Vec<String>> {
|
||||
match &self.store {
|
||||
DirectoryInner::Internal(store) => store.expn(address).await,
|
||||
DirectoryInner::Ldap(store) => store.expn(address).await,
|
||||
@@ -105,5 +112,6 @@ impl Directory {
|
||||
DirectoryInner::Smtp(store) => store.expn(address).await,
|
||||
DirectoryInner::Memory(store) => store.expn(address).await,
|
||||
}
|
||||
.caused_by( trc::location!())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,10 @@ use tokio::sync::oneshot;
|
||||
use totp_rs::TOTP;
|
||||
|
||||
use crate::backend::internal::SpecialSecrets;
|
||||
use crate::DirectoryError;
|
||||
use crate::Principal;
|
||||
|
||||
impl<T: serde::Serialize + serde::de::DeserializeOwned> Principal<T> {
|
||||
pub async fn verify_secret(&self, mut code: &str) -> crate::Result<bool> {
|
||||
pub async fn verify_secret(&self, mut code: &str) -> trc::Result<bool> {
|
||||
let mut totp_token = None;
|
||||
let mut is_totp_token_missing = false;
|
||||
let mut is_totp_required = false;
|
||||
@@ -59,7 +58,7 @@ impl<T: serde::Serialize + serde::de::DeserializeOwned> Principal<T> {
|
||||
|
||||
// Token needs to validate with at least one of the TOTP secrets
|
||||
is_totp_verified = TOTP::from_url(secret)
|
||||
.map_err(DirectoryError::InvalidTotpUrl)?
|
||||
.map_err(|err| trc::Cause::Invalid.reason(err).details(secret.to_string()))?
|
||||
.check_current(totp_token)
|
||||
.unwrap_or(false);
|
||||
}
|
||||
@@ -67,9 +66,9 @@ impl<T: serde::Serialize + serde::de::DeserializeOwned> Principal<T> {
|
||||
if let Some((_, app_secret)) =
|
||||
secret.strip_prefix("$app$").and_then(|s| s.split_once('$'))
|
||||
{
|
||||
is_app_authenticated = verify_secret_hash(app_secret, code).await;
|
||||
is_app_authenticated = verify_secret_hash(app_secret, code).await?;
|
||||
} else {
|
||||
is_authenticated = verify_secret_hash(secret, code).await;
|
||||
is_authenticated = verify_secret_hash(secret, code).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,7 +82,7 @@ impl<T: serde::Serialize + serde::de::DeserializeOwned> Principal<T> {
|
||||
// Only let the client know if the TOTP code is missing
|
||||
// if the password is correct
|
||||
|
||||
Err(DirectoryError::MissingTotpCode)
|
||||
Err(trc::Cause::MissingParameter.into_err())
|
||||
} else {
|
||||
// Return the TOTP verification status
|
||||
|
||||
@@ -97,7 +96,7 @@ impl<T: serde::Serialize + serde::de::DeserializeOwned> Principal<T> {
|
||||
if is_totp_verified {
|
||||
// TOTP URL appeared after password hash in secrets list
|
||||
for secret in &self.secrets {
|
||||
if secret.is_password() && verify_secret_hash(secret, code).await {
|
||||
if secret.is_password() && verify_secret_hash(secret, code).await? {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
@@ -108,7 +107,7 @@ impl<T: serde::Serialize + serde::de::DeserializeOwned> Principal<T> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn verify_hash_prefix(hashed_secret: &str, secret: &str) -> bool {
|
||||
async fn verify_hash_prefix(hashed_secret: &str, secret: &str) -> trc::Result<bool> {
|
||||
if hashed_secret.starts_with("$argon2")
|
||||
|| hashed_secret.starts_with("$pbkdf2")
|
||||
|| hashed_secret.starts_with("$scrypt")
|
||||
@@ -119,63 +118,49 @@ async fn verify_hash_prefix(hashed_secret: &str, secret: &str) -> bool {
|
||||
|
||||
tokio::task::spawn_blocking(move || match PasswordHash::new(&hashed_secret) {
|
||||
Ok(hash) => {
|
||||
tx.send(
|
||||
hash.verify_password(&[&Argon2::default(), &Pbkdf2, &Scrypt], &secret)
|
||||
.is_ok(),
|
||||
)
|
||||
.ok();
|
||||
tx.send(Ok(hash
|
||||
.verify_password(&[&Argon2::default(), &Pbkdf2, &Scrypt], &secret)
|
||||
.is_ok()))
|
||||
.ok();
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::warn!(
|
||||
context = "directory",
|
||||
event = "error",
|
||||
hash = hashed_secret,
|
||||
"Invalid password hash"
|
||||
);
|
||||
tx.send(false).ok();
|
||||
Err(err) => {
|
||||
tx.send(Err(trc::Cause::Invalid.reason(err).details(hashed_secret)))
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
|
||||
match rx.await {
|
||||
Ok(result) => result,
|
||||
Err(_) => {
|
||||
tracing::warn!(context = "directory", event = "error", "Thread join error");
|
||||
false
|
||||
}
|
||||
Err(err) => Err(trc::Cause::Thread.reason(err)),
|
||||
}
|
||||
} else if hashed_secret.starts_with("$2") {
|
||||
// Blowfish crypt
|
||||
bcrypt::verify(secret, hashed_secret)
|
||||
Ok(bcrypt::verify(secret, hashed_secret))
|
||||
} else if hashed_secret.starts_with("$6$") {
|
||||
// SHA-512 crypt
|
||||
sha512_crypt::verify(secret, hashed_secret)
|
||||
Ok(sha512_crypt::verify(secret, hashed_secret))
|
||||
} else if hashed_secret.starts_with("$5$") {
|
||||
// SHA-256 crypt
|
||||
sha256_crypt::verify(secret, hashed_secret)
|
||||
Ok(sha256_crypt::verify(secret, hashed_secret))
|
||||
} else if hashed_secret.starts_with("$sha1") {
|
||||
// SHA-1 crypt
|
||||
sha1_crypt::verify(secret, hashed_secret)
|
||||
Ok(sha1_crypt::verify(secret, hashed_secret))
|
||||
} else if hashed_secret.starts_with("$1") {
|
||||
// MD5 based hash
|
||||
md5_crypt::verify(secret, hashed_secret)
|
||||
Ok(md5_crypt::verify(secret, hashed_secret))
|
||||
} else {
|
||||
// Unknown hash
|
||||
tracing::warn!(
|
||||
context = "directory",
|
||||
event = "error",
|
||||
hash = hashed_secret,
|
||||
"Invalid password hash"
|
||||
);
|
||||
false
|
||||
Err(trc::Cause::Invalid
|
||||
.into_err()
|
||||
.details(hashed_secret.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn verify_secret_hash(hashed_secret: &str, secret: &str) -> bool {
|
||||
pub async fn verify_secret_hash(hashed_secret: &str, secret: &str) -> trc::Result<bool> {
|
||||
if hashed_secret.starts_with('$') {
|
||||
verify_hash_prefix(hashed_secret, secret).await
|
||||
} else if hashed_secret.starts_with('_') {
|
||||
// Enhanced DES-based hash
|
||||
bsdi_crypt::verify(secret, hashed_secret)
|
||||
Ok(bsdi_crypt::verify(secret, hashed_secret))
|
||||
} else if let Some(hashed_secret) = hashed_secret.strip_prefix('{') {
|
||||
if let Some((algo, hashed_secret)) = hashed_secret.split_once('}') {
|
||||
match algo {
|
||||
@@ -186,9 +171,13 @@ pub async fn verify_secret_hash(hashed_secret: &str, secret: &str) -> bool {
|
||||
// SHA-1
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(secret.as_bytes());
|
||||
String::from_utf8(base64_encode(&hasher.finalize()[..]).unwrap_or_default())
|
||||
Ok(
|
||||
String::from_utf8(
|
||||
base64_encode(&hasher.finalize()[..]).unwrap_or_default(),
|
||||
)
|
||||
.unwrap()
|
||||
== hashed_secret
|
||||
== hashed_secret,
|
||||
)
|
||||
}
|
||||
"SSHA" => {
|
||||
// Salted SHA-1
|
||||
@@ -198,15 +187,19 @@ pub async fn verify_secret_hash(hashed_secret: &str, secret: &str) -> bool {
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(secret.as_bytes());
|
||||
hasher.update(salt);
|
||||
&hasher.finalize()[..] == hash
|
||||
Ok(&hasher.finalize()[..] == hash)
|
||||
}
|
||||
"SHA256" => {
|
||||
// Verify hash
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(secret.as_bytes());
|
||||
String::from_utf8(base64_encode(&hasher.finalize()[..]).unwrap_or_default())
|
||||
Ok(
|
||||
String::from_utf8(
|
||||
base64_encode(&hasher.finalize()[..]).unwrap_or_default(),
|
||||
)
|
||||
.unwrap()
|
||||
== hashed_secret
|
||||
== hashed_secret,
|
||||
)
|
||||
}
|
||||
"SSHA256" => {
|
||||
// Salted SHA-256
|
||||
@@ -216,15 +209,19 @@ pub async fn verify_secret_hash(hashed_secret: &str, secret: &str) -> bool {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(secret.as_bytes());
|
||||
hasher.update(salt);
|
||||
&hasher.finalize()[..] == hash
|
||||
Ok(&hasher.finalize()[..] == hash)
|
||||
}
|
||||
"SHA512" => {
|
||||
// SHA-512
|
||||
let mut hasher = Sha512::new();
|
||||
hasher.update(secret.as_bytes());
|
||||
String::from_utf8(base64_encode(&hasher.finalize()[..]).unwrap_or_default())
|
||||
Ok(
|
||||
String::from_utf8(
|
||||
base64_encode(&hasher.finalize()[..]).unwrap_or_default(),
|
||||
)
|
||||
.unwrap()
|
||||
== hashed_secret
|
||||
== hashed_secret,
|
||||
)
|
||||
}
|
||||
"SSHA512" => {
|
||||
// Salted SHA-512
|
||||
@@ -234,43 +231,35 @@ pub async fn verify_secret_hash(hashed_secret: &str, secret: &str) -> bool {
|
||||
let mut hasher = Sha512::new();
|
||||
hasher.update(secret.as_bytes());
|
||||
hasher.update(salt);
|
||||
&hasher.finalize()[..] == hash
|
||||
Ok(&hasher.finalize()[..] == hash)
|
||||
}
|
||||
"MD5" => {
|
||||
// MD5
|
||||
let digest = md5::compute(secret.as_bytes());
|
||||
String::from_utf8(base64_encode(&digest[..]).unwrap_or_default()).unwrap()
|
||||
== hashed_secret
|
||||
Ok(
|
||||
String::from_utf8(base64_encode(&digest[..]).unwrap_or_default()).unwrap()
|
||||
== hashed_secret,
|
||||
)
|
||||
}
|
||||
"CRYPT" | "crypt" => {
|
||||
if hashed_secret.starts_with('$') {
|
||||
verify_hash_prefix(hashed_secret, secret).await
|
||||
} else {
|
||||
// Unix crypt
|
||||
unix_crypt::verify(secret, hashed_secret)
|
||||
Ok(unix_crypt::verify(secret, hashed_secret))
|
||||
}
|
||||
}
|
||||
"PLAIN" | "plain" | "CLEAR" | "clear" => hashed_secret == secret,
|
||||
_ => {
|
||||
tracing::warn!(
|
||||
context = "directory",
|
||||
event = "error",
|
||||
algorithm = algo,
|
||||
"Unsupported password hash algorithm"
|
||||
);
|
||||
false
|
||||
}
|
||||
"PLAIN" | "plain" | "CLEAR" | "clear" => Ok(hashed_secret == secret),
|
||||
_ => Err(trc::Cause::Invalid
|
||||
.ctx(trc::Key::Reason, "Unsupported algorithm")
|
||||
.details(hashed_secret.to_string())),
|
||||
}
|
||||
} else {
|
||||
tracing::warn!(
|
||||
context = "directory",
|
||||
event = "error",
|
||||
hash = hashed_secret,
|
||||
"Invalid password hash"
|
||||
);
|
||||
false
|
||||
Err(trc::Cause::Invalid
|
||||
.into_err()
|
||||
.details(hashed_secret.to_string()))
|
||||
}
|
||||
} else {
|
||||
hashed_secret == secret
|
||||
Ok(hashed_secret == secret)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,15 +5,11 @@
|
||||
*/
|
||||
|
||||
use core::cache::CachedDirectory;
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
sync::Arc,
|
||||
};
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
use ahash::AHashMap;
|
||||
use backend::{
|
||||
imap::{ImapDirectory, ImapError},
|
||||
internal::PrincipalField,
|
||||
ldap::LdapDirectory,
|
||||
memory::MemoryDirectory,
|
||||
smtp::SmtpDirectory,
|
||||
@@ -23,7 +19,6 @@ use deadpool::managed::PoolError;
|
||||
use ldap3::LdapError;
|
||||
use mail_send::Credentials;
|
||||
use store::Store;
|
||||
use totp_rs::TotpUrlError;
|
||||
|
||||
pub mod backend;
|
||||
pub mod core;
|
||||
@@ -72,30 +67,6 @@ pub enum Type {
|
||||
Other = 6,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DirectoryError {
|
||||
Ldap(LdapError),
|
||||
Store(store::Error),
|
||||
Imap(ImapError),
|
||||
Smtp(mail_send::Error),
|
||||
Pool(String),
|
||||
Management(ManagementError),
|
||||
TimedOut,
|
||||
Unsupported,
|
||||
InvalidTotpUrl(TotpUrlError),
|
||||
MissingTotpCode,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ManagementError {
|
||||
MissingField(PrincipalField),
|
||||
AlreadyExists {
|
||||
field: PrincipalField,
|
||||
value: String,
|
||||
},
|
||||
NotFound(String),
|
||||
}
|
||||
|
||||
pub enum DirectoryInner {
|
||||
Internal(Store),
|
||||
Ldap(LdapDirectory),
|
||||
@@ -158,38 +129,6 @@ pub struct Directories {
|
||||
pub directories: AHashMap<String, Arc<Directory>>,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, DirectoryError>;
|
||||
|
||||
impl From<PoolError<LdapError>> for DirectoryError {
|
||||
fn from(error: PoolError<LdapError>) -> Self {
|
||||
match error {
|
||||
PoolError::Backend(error) => error.into(),
|
||||
PoolError::Timeout(_) => DirectoryError::timeout("ldap"),
|
||||
error => DirectoryError::Pool(error.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PoolError<ImapError>> for DirectoryError {
|
||||
fn from(error: PoolError<ImapError>) -> Self {
|
||||
match error {
|
||||
PoolError::Backend(error) => error.into(),
|
||||
PoolError::Timeout(_) => DirectoryError::timeout("imap"),
|
||||
error => DirectoryError::Pool(error.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PoolError<mail_send::Error>> for DirectoryError {
|
||||
fn from(error: PoolError<mail_send::Error>) -> Self {
|
||||
match error {
|
||||
PoolError::Backend(error) => error.into(),
|
||||
PoolError::Timeout(_) => DirectoryError::timeout("smtp"),
|
||||
error => DirectoryError::Pool(error.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Principal<u32> {
|
||||
pub fn fallback_admin(fallback_pass: impl Into<String>) -> Self {
|
||||
Principal {
|
||||
@@ -211,109 +150,70 @@ impl<T: Ord> Principal<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LdapError> for DirectoryError {
|
||||
fn from(error: LdapError) -> Self {
|
||||
tracing::warn!(
|
||||
context = "directory",
|
||||
event = "error",
|
||||
protocol = "ldap",
|
||||
reason = %error,
|
||||
"LDAP directory error"
|
||||
);
|
||||
|
||||
DirectoryError::Ldap(error)
|
||||
}
|
||||
trait IntoError {
|
||||
fn into_error(self) -> trc::Error;
|
||||
}
|
||||
|
||||
impl From<store::Error> for DirectoryError {
|
||||
fn from(error: store::Error) -> Self {
|
||||
tracing::warn!(
|
||||
context = "directory",
|
||||
event = "error",
|
||||
protocol = "store",
|
||||
reason = %error,
|
||||
"Directory error"
|
||||
);
|
||||
|
||||
DirectoryError::Store(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ImapError> for DirectoryError {
|
||||
fn from(error: ImapError) -> Self {
|
||||
tracing::warn!(
|
||||
context = "directory",
|
||||
event = "error",
|
||||
protocol = "imap",
|
||||
reason = %error,
|
||||
"IMAP directory error"
|
||||
);
|
||||
|
||||
DirectoryError::Imap(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mail_send::Error> for DirectoryError {
|
||||
fn from(error: mail_send::Error) -> Self {
|
||||
tracing::warn!(
|
||||
context = "directory",
|
||||
event = "error",
|
||||
protocol = "smtp",
|
||||
reason = %error,
|
||||
"SMTP directory error"
|
||||
);
|
||||
|
||||
DirectoryError::Smtp(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl DirectoryError {
|
||||
pub fn unsupported(protocol: &str, method: &str) -> Self {
|
||||
tracing::warn!(
|
||||
context = "directory",
|
||||
event = "error",
|
||||
protocol = protocol,
|
||||
method = method,
|
||||
"Method not supported by directory"
|
||||
);
|
||||
DirectoryError::Unsupported
|
||||
}
|
||||
|
||||
pub fn timeout(protocol: &str) -> Self {
|
||||
tracing::warn!(
|
||||
context = "directory",
|
||||
event = "error",
|
||||
protocol = protocol,
|
||||
"Directory timed out"
|
||||
);
|
||||
DirectoryError::TimedOut
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for DirectoryError {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Store(l0), Self::Store(r0)) => l0 == r0,
|
||||
(Self::Pool(l0), Self::Pool(r0)) => l0 == r0,
|
||||
(Self::Management(l0), Self::Management(r0)) => l0 == r0,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DirectoryError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
impl IntoError for PoolError<LdapError> {
|
||||
fn into_error(self) -> trc::Error {
|
||||
match self {
|
||||
Self::Ldap(error) => write!(f, "LDAP error: {}", error),
|
||||
Self::Store(error) => write!(f, "Store error: {}", error),
|
||||
Self::Imap(error) => write!(f, "IMAP error: {}", error),
|
||||
Self::Smtp(error) => write!(f, "SMTP error: {}", error),
|
||||
Self::Pool(error) => write!(f, "Pool error: {}", error),
|
||||
Self::Management(error) => write!(f, "Management error: {:?}", error),
|
||||
Self::TimedOut => write!(f, "Directory timed out"),
|
||||
Self::Unsupported => write!(f, "Method not supported by directory"),
|
||||
Self::InvalidTotpUrl(error) => write!(f, "Invalid TOTP URL: {}", error),
|
||||
Self::MissingTotpCode => write!(f, "Missing TOTP code"),
|
||||
PoolError::Backend(error) => error.into_error(),
|
||||
PoolError::Timeout(_) => {
|
||||
trc::Cause::Timeout.ctx(trc::Key::Protocol, trc::Protocol::Ldap)
|
||||
}
|
||||
err => trc::Cause::Pool
|
||||
.ctx(trc::Key::Protocol, trc::Protocol::Ldap)
|
||||
.reason(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoError for PoolError<ImapError> {
|
||||
fn into_error(self) -> trc::Error {
|
||||
match self {
|
||||
PoolError::Backend(error) => error.into_error(),
|
||||
PoolError::Timeout(_) => {
|
||||
trc::Cause::Timeout.ctx(trc::Key::Protocol, trc::Protocol::Imap)
|
||||
}
|
||||
err => trc::Cause::Pool
|
||||
.ctx(trc::Key::Protocol, trc::Protocol::Imap)
|
||||
.reason(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoError for PoolError<mail_send::Error> {
|
||||
fn into_error(self) -> trc::Error {
|
||||
match self {
|
||||
PoolError::Backend(error) => error.into_error(),
|
||||
PoolError::Timeout(_) => {
|
||||
trc::Cause::Timeout.ctx(trc::Key::Protocol, trc::Protocol::Smtp)
|
||||
}
|
||||
err => trc::Cause::Pool
|
||||
.ctx(trc::Key::Protocol, trc::Protocol::Smtp)
|
||||
.reason(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoError for ImapError {
|
||||
fn into_error(self) -> trc::Error {
|
||||
trc::Cause::Imap.reason(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoError for mail_send::Error {
|
||||
fn into_error(self) -> trc::Error {
|
||||
trc::Cause::Smtp.reason(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoError for LdapError {
|
||||
fn into_error(self) -> trc::Error {
|
||||
if let LdapError::LdapResult { result } = &self {
|
||||
trc::Cause::Ldap.ctx(trc::Key::Code, result.rc).reason(self)
|
||||
} else {
|
||||
trc::Cause::Ldap.reason(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user