Masked email: owners send as their live masks, over JMAP identities and SMTP submission (ME-11)

This commit is contained in:
2026-09-18 16:23:49 -07:00
parent 60d1d8b84a
commit 53ccc8f4de
4 changed files with 31 additions and 0 deletions
+13
View File
@@ -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<bool> {
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 {
+8
View File
@@ -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,
+1
View File
@@ -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"] }
+9
View File
@@ -248,6 +248,15 @@ impl<T: SessionStream> Session<T> {
.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),