Import upstream v0.16.22, stripped

Upstream commit: 474dd0229cb20cf513036619781ed97bd8073c3f
Enterprise-only files removed or emptied: 63
Enterprise-only snippets removed: 117 in 50 files
Dangling module declarations removed: 5
Cargo edits turning enterprise off: 14
Verification: clean
Enterprise feature gates left for rebuilt features: 19 in 18 files

Produced by tools/fork/strip.py. The full report is in docs/fork/strip-reports/ on main.
This commit is contained in:
2026-09-18 10:21:56 -07:00
commit 7dae9b29fd
1650 changed files with 485521 additions and 0 deletions
@@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{SqlDirectory, SqlMappings};
use crate::Directory;
use registry::schema::structs;
use store::Store;
#[allow(unreachable_patterns)]
impl SqlDirectory {
pub async fn open(
config: structs::SqlDirectory,
data_store: &Store,
) -> Result<Directory, String> {
let sql_store = match config.store {
#[cfg(feature = "postgres")]
structs::SqlAuthStore::PostgreSql(store) => {
store::backend::postgres::PostgresStore::open(store).await?
}
#[cfg(feature = "mysql")]
structs::SqlAuthStore::MySql(store) => {
store::backend::mysql::MysqlStore::open(store).await?
}
#[cfg(feature = "sqlite")]
structs::SqlAuthStore::Sqlite(store) => {
store::backend::sqlite::SqliteStore::open(store)?
}
structs::SqlAuthStore::Default => {
if data_store.is_sql() {
data_store.clone()
} else {
return Err(concat!(
"This directory is set to store accounts in the main data store, ",
"but the configured data store is not an SQL database. ",
"Either select an SQL data store or configure a separate SQL ",
"database for this directory."
)
.to_string());
}
}
_ => {
return Err(
"Binary not compiled with support for the selected SQL directory backend."
.to_string(),
);
}
};
let mappings = SqlMappings {
query_login: config.query_login,
query_recipient: config.query_recipient,
query_member_of: config.query_member_of,
query_email_aliases: config.query_email_aliases,
column_email: config.column_email,
column_secret: config.column_secret,
column_type: config.column_class,
column_description: config.column_description,
};
Ok(Directory::Sql(SqlDirectory {
sql_store,
mappings,
}))
}
}
+201
View File
@@ -0,0 +1,201 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::{SqlDirectory, SqlMappings};
use crate::{Account, Credentials, Recipient, core::secret::verify_secret_hash};
use store::{NamedRows, Rows, Value};
use trc::AddContext;
use utils::sanitize_email;
impl SqlDirectory {
pub async fn authenticate(&self, credentials: &Credentials) -> trc::Result<Account> {
let (username, secret) = match credentials {
Credentials::Basic {
username, secret, ..
} => (username, secret),
Credentials::Bearer { .. } => {
return Err(trc::AuthEvent::Error
.into_err()
.details("Unsupported credentials type for SQL authentication"));
}
};
if secret.is_empty() {
return Err(trc::AuthEvent::Failed
.into_err()
.details("Empty secret rejected")
.ctx(trc::Key::AccountName, username.to_string()));
}
let Recipient::Account(mut account) = self.mappings.row_to_account(
self.sql_store
.sql_query::<NamedRows>(&self.mappings.query_login, vec![username.into()])
.await
.caused_by(trc::location!())?,
) else {
return Err(trc::AuthEvent::Failed
.into_err()
.details("SQL login query did not return an account")
.ctx(trc::Key::AccountName, username.to_string()));
};
// Validate secret
if let Some(account_secret) = &account.secret {
if !verify_secret_hash(account_secret, secret.as_bytes()).await? {
return Err(trc::AuthEvent::Failed
.into_err()
.details("Invalid credentials")
.ctx(trc::Key::AccountName, username.to_string()));
}
} else {
return Err(trc::AuthEvent::Error
.into_err()
.details("Account does not have a secret")
.ctx(trc::Key::AccountName, username.to_string()));
}
// Obtain members
if let Some(query) = &self.mappings.query_member_of {
let members = account.groups.get_or_insert_default();
for row in self
.sql_store
.sql_query::<Rows>(query, vec![username.into()])
.await
.caused_by(trc::location!())?
.rows
{
if let Some(Value::Text(address)) = row.values.first()
&& let Some(email) = sanitize_email(address)
{
members.push(email);
}
}
}
// Obtain emails
if let Some(query) = &self.mappings.query_email_aliases {
account.email_aliases.extend(
self.sql_store
.sql_query::<Rows>(query, vec![username.into()])
.await
.caused_by(trc::location!())?
.rows
.into_iter()
.flat_map(|v| {
v.values
.into_iter()
.filter_map(|v| sanitize_email(v.to_str().as_ref()))
}),
);
}
if account.email.is_empty() {
account.email = sanitize_email(username).unwrap_or_else(|| username.to_lowercase());
}
Ok(account)
}
pub async fn recipient(&self, address: &str) -> trc::Result<Recipient> {
let recipient = self.mappings.row_to_account(
self.sql_store
.sql_query::<NamedRows>(&self.mappings.query_recipient, vec![address.into()])
.await
.caused_by(trc::location!())?,
);
match recipient {
Recipient::Account(mut account) => {
// Obtain members
if let Some(query) = &self.mappings.query_member_of {
let members = account.groups.get_or_insert_default();
for row in self
.sql_store
.sql_query::<Rows>(query, vec![account.email.as_str().into()])
.await
.caused_by(trc::location!())?
.rows
{
if let Some(Value::Text(address)) = row.values.first()
&& let Some(email) = sanitize_email(address)
{
members.push(email);
}
}
}
// Obtain emails
if let Some(query) = &self.mappings.query_email_aliases {
account.email_aliases.extend(
self.sql_store
.sql_query::<Rows>(query, vec![account.email.as_str().into()])
.await
.caused_by(trc::location!())?
.rows
.into_iter()
.flat_map(|v| {
v.values
.into_iter()
.filter_map(|v| sanitize_email(v.to_str().as_ref()))
}),
);
}
Ok(Recipient::Account(account))
}
Recipient::Group(group) => Ok(Recipient::Group(group)),
Recipient::Invalid => Ok(Recipient::Invalid),
}
}
}
impl SqlMappings {
pub fn row_to_account(&self, rows: NamedRows) -> Recipient {
if rows.rows.is_empty() {
return Recipient::Invalid;
}
let mut account = Account::default();
let mut is_group = false;
if let Some(row) = rows.rows.into_iter().next() {
for (name, value) in rows.names.into_iter().zip(row.values) {
if name.eq_ignore_ascii_case(&self.column_email) {
if let Value::Text(text) = value
&& let Some(email) = sanitize_email(&text)
{
account.email = email;
}
} else if name.eq_ignore_ascii_case(&self.column_secret) {
if let Value::Text(text) = value
&& !text.is_empty()
{
account.secret = Some(text.into_owned());
}
} else if let Some(column_type) = &self.column_type
&& name.eq_ignore_ascii_case(column_type)
{
is_group = value.to_str().eq_ignore_ascii_case("group");
} else if let Some(column_description) = &self.column_description
&& name.eq_ignore_ascii_case(column_description)
&& let Value::Text(text) = value
&& !text.is_empty()
{
account.description = Some(text.into_owned());
}
}
}
if !is_group {
Recipient::Account(account)
} else {
Recipient::Group(crate::Group {
email: account.email,
email_aliases: account.email_aliases,
description: account.description,
})
}
}
}
+27
View File
@@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use store::Store;
pub mod config;
pub mod lookup;
pub struct SqlDirectory {
sql_store: Store,
mappings: SqlMappings,
}
#[derive(Debug, Default)]
pub(crate) struct SqlMappings {
query_login: String,
query_recipient: String,
query_member_of: Option<String>,
query_email_aliases: Option<String>,
column_email: String,
column_secret: String,
column_type: Option<String>,
column_description: Option<String>,
}