diff --git a/crates/features/src/masked_email/ops.rs b/crates/features/src/masked_email/ops.rs index fafc961..82af2c3 100644 --- a/crates/features/src/masked_email/ops.rs +++ b/crates/features/src/masked_email/ops.rs @@ -333,6 +333,19 @@ pub async fn ensure_indexed(data: &Store, registry: &RegistryStore) -> trc::Resu data.write(batch.build_all()).await.map(|_| ()) } +/// Whether an account may send as an address because it's one of the +/// account's live masks (ME-11). +pub async fn sends_as( + data: &Store, + registry: &RegistryStore, + account_id: u32, + address: &str, +) -> trc::Result { + Ok(resolve(data, registry, address) + .await? + .is_some_and(|mask| mask.object.account_id.document_id() == account_id)) +} + /// What an address is, as far as masks go. #[derive(Debug)] pub enum Lookup { diff --git a/crates/jmap/src/identity/set.rs b/crates/jmap/src/identity/set.rs index f52cd71..3a542a5 100644 --- a/crates/jmap/src/identity/set.rs +++ b/crates/jmap/src/identity/set.rs @@ -75,6 +75,14 @@ impl IdentitySet for Server { .addresses() .iter() .any(|e| e == &identity.email) + // inbuxa: ME-11: a live mask of the account's own is an identity too + && !inbuxa_features::masked_email::ops::sends_as( + &self.core.storage.data, + self.registry(), + account_id, + &identity.email, + ) + .await? { response.not_created.append( id, diff --git a/crates/smtp/Cargo.toml b/crates/smtp/Cargo.toml index 2bdd6b7..1581c7a 100644 --- a/crates/smtp/Cargo.toml +++ b/crates/smtp/Cargo.toml @@ -18,6 +18,7 @@ directory = { path = "../directory" } common = { path = "../common" } email = { path = "../email" } registry = { path = "../registry" } +inbuxa-features = { path = "../features" } spam-filter = { path = "../spam-filter" } trc = { path = "../trc" } mail-auth = { version = "0.13", features = ["rkyv"] } diff --git a/crates/smtp/src/inbound/mail.rs b/crates/smtp/src/inbound/mail.rs index 482f191..332d622 100644 --- a/crates/smtp/src/inbound/mail.rs +++ b/crates/smtp/src/inbound/mail.rs @@ -248,6 +248,15 @@ impl Session { .authenticated_emails() .iter() .any(|e| e == address_lcase) + // inbuxa: ME-11: the sender's own live masks are its addresses too + && !inbuxa_features::masked_email::ops::sends_as( + &self.server.core.storage.data, + self.server.registry(), + self.data.authenticated_as.as_ref().unwrap().account_id, + address_lcase, + ) + .await + .unwrap_or(false) { trc::event!( Smtp(SmtpEvent::MailFromUnauthorized),