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
+204
View File
@@ -0,0 +1,204 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::net::IpAddr;
use mail_auth::IpLookupStrategy;
use sieve::{FunctionMap, runtime::Variable};
use super::PluginContext;
pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap) {
fnc_map.set_external_function("dns_query", plugin_id, 2);
}
pub fn register_exists(plugin_id: u32, fnc_map: &mut FunctionMap) {
fnc_map.set_external_function("dns_exists", plugin_id, 2);
}
pub async fn exec(ctx: PluginContext<'_>) -> trc::Result<Variable> {
let entry = ctx.arguments[0].to_string();
let record_type = ctx.arguments[1].to_string();
Ok(if record_type.eq_ignore_ascii_case("ip") {
match ctx
.server
.core
.smtp
.resolvers
.dns
.ip_lookup(
entry.as_ref(),
IpLookupStrategy::Ipv4thenIpv6,
10,
Some(&ctx.server.inner.cache.dns_ipv4),
Some(&ctx.server.inner.cache.dns_ipv6),
)
.await
{
Ok(result) => result
.iter()
.map(|ip| Variable::from(ip.to_string()))
.collect::<Vec<_>>()
.into(),
Err(err) => err.short_error().into(),
}
} else if record_type.eq_ignore_ascii_case("mx") {
match ctx
.server
.core
.smtp
.resolvers
.dns
.mx_lookup(entry.as_ref(), Some(&ctx.server.inner.cache.dns_mx))
.await
{
Ok(result) => result
.rrset
.iter()
.flat_map(|mx| {
mx.exchanges
.iter()
.map(|host| Variable::from(format!("{} {}", mx.preference, host)))
})
.collect::<Vec<_>>()
.into(),
Err(err) => err.short_error().into(),
}
} else if record_type.eq_ignore_ascii_case("txt") {
#[cfg(feature = "test_mode")]
{
if entry.contains("origin") {
return Ok(Variable::from("23028|US|arin|2002-01-04".to_string()));
}
}
match ctx
.server
.core
.smtp
.resolvers
.dns
.txt_raw_lookup(entry.as_ref())
.await
{
Ok(result) => Variable::from(String::from_utf8(result).unwrap_or_default()),
Err(err) => err.short_error().into(),
}
} else if record_type.eq_ignore_ascii_case("ptr") {
if let Ok(addr) = entry.parse::<IpAddr>() {
match ctx
.server
.core
.smtp
.resolvers
.dns
.ptr_lookup(addr, Some(&ctx.server.inner.cache.dns_ptr))
.await
{
Ok(result) => result
.rrset
.iter()
.map(|host| Variable::from(host.to_string()))
.collect::<Vec<_>>()
.into(),
Err(err) => err.short_error().into(),
}
} else {
Variable::default()
}
} else if record_type.eq_ignore_ascii_case("ipv4") {
#[cfg(feature = "test_mode")]
{
if entry.contains(".168.192.") {
let parts = entry.split('.').collect::<Vec<_>>();
return Ok(vec![Variable::from(format!("127.0.{}.{}", parts[1], parts[0]))].into());
}
}
match ctx
.server
.core
.smtp
.resolvers
.dns
.ipv4_lookup(entry.as_ref(), Some(&ctx.server.inner.cache.dns_ipv4))
.await
{
Ok(result) => result
.rrset
.iter()
.map(|ip| Variable::from(ip.to_string()))
.collect::<Vec<_>>()
.into(),
Err(err) => err.short_error().into(),
}
} else if record_type.eq_ignore_ascii_case("ipv6") {
match ctx
.server
.core
.smtp
.resolvers
.dns
.ipv6_lookup(entry.as_ref(), Some(&ctx.server.inner.cache.dns_ipv6))
.await
{
Ok(result) => result
.rrset
.iter()
.map(|ip| Variable::from(ip.to_string()))
.collect::<Vec<_>>()
.into(),
Err(err) => err.short_error().into(),
}
} else {
Variable::default()
})
}
pub async fn exec_exists(ctx: PluginContext<'_>) -> trc::Result<Variable> {
let entry = ctx.arguments[0].to_string();
let record_type = ctx.arguments[1].to_string();
let result = if record_type.eq_ignore_ascii_case("ip") {
ctx.server.dns_exists_ip(entry.as_ref()).await
} else if record_type.eq_ignore_ascii_case("mx") {
ctx.server.dns_exists_mx(entry.as_ref()).await
} else if record_type.eq_ignore_ascii_case("ptr") {
ctx.server.dns_exists_ptr(entry.as_ref()).await
} else if record_type.eq_ignore_ascii_case("ipv4") {
#[cfg(feature = "test_mode")]
{
if entry.starts_with("2.0.168.192.") {
return Ok(1.into());
}
}
ctx.server.dns_exists_ipv4(entry.as_ref()).await
} else if record_type.eq_ignore_ascii_case("ipv6") {
ctx.server.dns_exists_ipv6(entry.as_ref()).await
} else {
return Ok((-1).into());
};
Ok(result.map(i64::from).unwrap_or(-1).into())
}
trait ShortError {
fn short_error(&self) -> &'static str;
}
impl ShortError for mail_auth::Error {
fn short_error(&self) -> &'static str {
match self {
mail_auth::Error::Dns(mail_auth::DnsError::Resolver(_)) => "temp_fail",
mail_auth::Error::Dns(mail_auth::DnsError::RecordNotFound(_)) => "not_found",
mail_auth::Error::Io(_) => "io_error",
mail_auth::Error::Dns(mail_auth::DnsError::InvalidRecordType) => "invalid_record",
_ => "unknown_error",
}
}
}
+50
View File
@@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::process::Command;
use sieve::{FunctionMap, runtime::Variable};
use super::PluginContext;
pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap) {
fnc_map.set_external_function("exec", plugin_id, 2);
}
pub async fn exec(ctx: PluginContext<'_>) -> trc::Result<Variable> {
let mut arguments = ctx.arguments.into_iter();
tokio::task::spawn_blocking(move || {
let command = arguments
.next()
.map(|a| a.to_string().into_owned())
.unwrap_or_default();
match Command::new(&command)
.args(
arguments
.next()
.map(|a| a.into_string_array())
.unwrap_or_default(),
)
.output()
{
Ok(result) => Ok(result.status.success()),
Err(err) => Err(trc::SieveEvent::RuntimeError
.ctx(trc::Key::Path, command)
.reason(err)
.details("Failed to execute command")),
}
})
.await
.map_err(|err| {
trc::EventType::Server(trc::ServerEvent::ThreadError)
.reason(err)
.caused_by(trc::location!())
.details("Join Error")
})?
.map(Into::into)
}
@@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use sieve::{FunctionMap, runtime::Variable};
use crate::scripts::ScriptModification;
use super::PluginContext;
pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap) {
fnc_map.set_external_function("add_header", plugin_id, 2);
}
pub fn exec(ctx: PluginContext<'_>) -> trc::Result<Variable> {
Ok(if let (Variable::String(name), Variable::String(value)) =
(&ctx.arguments[0], &ctx.arguments[1])
{
ctx.modifications.push(ScriptModification::AddHeader {
name: name.clone(),
value: value.clone(),
});
true
} else {
false
}
.into())
}
+52
View File
@@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::time::Duration;
use reqwest::header::USER_AGENT;
use sieve::{FunctionMap, runtime::Variable};
use super::PluginContext;
pub fn register_header(plugin_id: u32, fnc_map: &mut FunctionMap) {
fnc_map.set_external_function("http_header", plugin_id, 4);
}
pub async fn exec_header(ctx: PluginContext<'_>) -> trc::Result<Variable> {
let url = ctx.arguments[0].to_string();
let header = ctx.arguments[1].to_string();
let agent = ctx.arguments[2].to_string();
let timeout = ctx.arguments[3].to_string().parse::<u64>().unwrap_or(5000);
#[cfg(feature = "test_mode")]
if url.contains("redirect.") {
return Ok(Variable::from(url.split_once("/?").unwrap().1.to_string()));
}
ctx.server
.core
.sieve
.http_client
.get(url.as_ref())
.header(USER_AGENT, agent.as_ref())
.timeout(Duration::from_millis(timeout))
.send()
.await
.map_err(|err| {
trc::SieveEvent::RuntimeError
.into_err()
.reason(err)
.details("Failed to send request")
})
.map(|response| {
response
.headers()
.get(header.as_ref())
.and_then(|h| h.to_str().ok())
.map(|h| Variable::from(h.to_string()))
.unwrap_or_default()
})
}
@@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use sieve::{FunctionMap, compiler::Number, runtime::Variable};
use std::time::Instant;
use trc::{AiEvent, SecurityEvent};
use super::PluginContext;
pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap) {
fnc_map.set_external_function("llm_prompt", plugin_id, 3);
}
pub async fn exec(ctx: PluginContext<'_>) -> trc::Result<Variable> {
Ok(false.into())
}
+145
View File
@@ -0,0 +1,145 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::PluginContext;
use crate::scripts::into_sieve_value;
use sieve::{FunctionMap, runtime::Variable};
use store::{Deserialize, Value, dispatch::lookup::KeyValue};
pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap) {
fnc_map.set_external_function("key_exists", plugin_id, 2);
}
pub fn register_get(plugin_id: u32, fnc_map: &mut FunctionMap) {
fnc_map.set_external_function("key_get", plugin_id, 2);
}
pub fn register_set(plugin_id: u32, fnc_map: &mut FunctionMap) {
fnc_map.set_external_function("key_set", plugin_id, 4);
}
pub fn register_local_domain(plugin_id: u32, fnc_map: &mut FunctionMap) {
fnc_map.set_external_function("is_local_domain", plugin_id, 1);
}
pub async fn exec(ctx: PluginContext<'_>) -> trc::Result<Variable> {
let store = match &ctx.arguments[0] {
Variable::String(v) if !v.is_empty() => ctx.server.get_lookup_store(v.as_str()),
_ => Some(ctx.server.core.storage.memory.clone()),
}
.ok_or_else(|| {
trc::SieveEvent::RuntimeError
.ctx(trc::Key::Id, ctx.arguments[0].to_string().into_owned())
.details("Unknown store")
})?;
Ok(match &ctx.arguments[1] {
Variable::Array(items) => {
for item in items.iter() {
if !item.is_empty() && store.key_exists(item.to_string()).await? {
return Ok(true.into());
}
}
false
}
v if !v.is_empty() => store.key_exists(v.to_string()).await?,
_ => false,
}
.into())
}
pub async fn exec_get(ctx: PluginContext<'_>) -> trc::Result<Variable> {
match &ctx.arguments[0] {
Variable::String(v) if !v.is_empty() => ctx.server.get_lookup_store(v.as_str()),
_ => Some(ctx.server.core.storage.memory.clone()),
}
.ok_or_else(|| {
trc::SieveEvent::RuntimeError
.ctx(trc::Key::Id, ctx.arguments[0].to_string().into_owned())
.details("Unknown store")
})?
.key_get::<VariableWrapper>(ctx.arguments[1].to_string())
.await
.map(|v| v.map(|v| v.into_inner()).unwrap_or_default())
}
pub async fn exec_set(ctx: PluginContext<'_>) -> trc::Result<Variable> {
let expires = match &ctx.arguments[3] {
Variable::Integer(v) => Some(*v as u64),
Variable::Float(v) => Some(*v as u64),
_ => None,
};
match &ctx.arguments[0] {
Variable::String(v) if !v.is_empty() => ctx.server.get_lookup_store(v.as_str()),
_ => Some(ctx.server.core.storage.memory.clone()),
}
.ok_or_else(|| {
trc::SieveEvent::RuntimeError
.ctx(trc::Key::Id, ctx.arguments[0].to_string().into_owned())
.details("Unknown store")
})?
.key_set(
KeyValue::new(
ctx.arguments[1].to_string().into_owned().into_bytes(),
if !ctx.arguments[2].is_empty() {
bincode::serde::encode_to_vec(&ctx.arguments[2], bincode::config::standard())
.unwrap_or_default()
} else {
vec![]
},
)
.expires_opt(expires),
)
.await
.map(|_| true.into())
}
pub async fn exec_local_domain(ctx: PluginContext<'_>) -> trc::Result<Variable> {
let domain = ctx.arguments[0].to_string();
if !domain.is_empty() {
ctx.server
.domain(domain.as_ref())
.await
.map(|result| Variable::from(result.is_some()))
} else {
Ok(Variable::default())
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct VariableWrapper(Variable);
impl Deserialize for VariableWrapper {
fn deserialize(bytes: &[u8]) -> trc::Result<Self> {
Ok(VariableWrapper(
bincode::serde::decode_from_slice::<Variable, _>(bytes, bincode::config::standard())
.map(|v| v.0)
.unwrap_or_else(|_| {
Variable::String(String::from_utf8_lossy(bytes).into_owned().into())
}),
))
}
}
impl From<i64> for VariableWrapper {
fn from(value: i64) -> Self {
VariableWrapper(value.into())
}
}
impl VariableWrapper {
pub fn into_inner(self) -> Variable {
self.0
}
}
impl From<Value<'static>> for VariableWrapper {
fn from(value: Value<'static>) -> Self {
VariableWrapper(into_sieve_value(value))
}
}
+113
View File
@@ -0,0 +1,113 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
pub mod dns;
pub mod exec;
pub mod headers;
pub mod http;
pub mod llm_prompt;
pub mod lookup;
pub mod query;
pub mod text;
use mail_parser::Message;
use sieve::{FunctionMap, Input, runtime::Variable};
use crate::{Core, Server, auth::AccessToken};
use super::ScriptModification;
type RegisterPluginFnc = fn(u32, &mut FunctionMap) -> ();
pub struct PluginContext<'x> {
pub session_id: u64,
pub access_token: Option<&'x AccessToken>,
pub server: &'x Server,
pub message: &'x Message<'x>,
pub modifications: &'x mut Vec<ScriptModification>,
pub arguments: Vec<Variable>,
}
const PLUGINS_REGISTER: [RegisterPluginFnc; 13] = [
query::register,
exec::register,
lookup::register,
lookup::register_get,
lookup::register_set,
lookup::register_local_domain,
dns::register,
dns::register_exists,
http::register_header,
headers::register,
text::register_tokenize,
text::register_domain_part,
llm_prompt::register,
];
pub trait RegisterSievePlugins {
fn register_plugins_trusted(self) -> Self;
fn register_plugins_untrusted(self) -> Self;
}
impl RegisterSievePlugins for FunctionMap {
fn register_plugins_trusted(mut self) -> Self {
#[cfg(feature = "test_mode")]
{
self.set_external_function("print", PLUGINS_REGISTER.len() as u32, 1)
}
for (i, fnc) in PLUGINS_REGISTER.iter().enumerate() {
fnc(i as u32, &mut self);
}
self
}
fn register_plugins_untrusted(mut self) -> Self {
llm_prompt::register(12, &mut self);
self
}
}
impl Core {
pub async fn run_plugin(&self, id: u32, ctx: PluginContext<'_>) -> Input {
#[cfg(feature = "test_mode")]
if id == PLUGINS_REGISTER.len() as u32 {
return test_print(ctx);
}
let session_id = ctx.session_id;
let result = match id {
0 => query::exec(ctx).await,
1 => exec::exec(ctx).await,
2 => lookup::exec(ctx).await,
3 => lookup::exec_get(ctx).await,
4 => lookup::exec_set(ctx).await,
5 => lookup::exec_local_domain(ctx).await,
6 => dns::exec(ctx).await,
7 => dns::exec_exists(ctx).await,
8 => http::exec_header(ctx).await,
9 => headers::exec(ctx),
10 => text::exec_tokenize(ctx),
11 => text::exec_domain_part(ctx),
12 => llm_prompt::exec(ctx).await,
_ => unreachable!(),
};
match result {
Ok(result) => result.into(),
Err(err) => {
trc::error!(err.span_id(session_id).details("Sieve runtime error"));
Input::FncResult(Variable::default())
}
}
}
}
#[cfg(feature = "test_mode")]
pub fn test_print(ctx: PluginContext<'_>) -> Input {
println!("{}", ctx.arguments[0].to_string());
Input::True
}
@@ -0,0 +1,94 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use super::PluginContext;
use crate::scripts::{into_sieve_value, to_store_value};
use sieve::{FunctionMap, runtime::Variable};
use std::cmp::Ordering;
use store::{Rows, Value};
pub fn register(plugin_id: u32, fnc_map: &mut FunctionMap) {
fnc_map.set_external_function("query", plugin_id, 3);
}
pub async fn exec(ctx: PluginContext<'_>) -> trc::Result<Variable> {
// Obtain store name
let store = match &ctx.arguments[0] {
Variable::String(v) if !v.is_empty() => ctx
.server
.get_lookup_store(v.as_str())
.and_then(|v| v.into_store()),
_ => Some(ctx.server.core.storage.data.clone()),
}
.ok_or_else(|| {
trc::SieveEvent::RuntimeError
.ctx(trc::Key::Id, ctx.arguments[0].to_string().into_owned())
.details("Unknown store")
})?;
// Obtain query string
let query = ctx.arguments[1].to_string();
if query.is_empty() {
trc::bail!(
trc::SieveEvent::RuntimeError
.ctx(trc::Key::Id, ctx.arguments[0].to_string().into_owned())
.details("Empty query string")
);
}
// Obtain arguments
let arguments = match &ctx.arguments[2] {
Variable::Array(l) => l.iter().map(to_store_value).collect(),
v => vec![to_store_value(v)],
};
// Run query
if query
.as_bytes()
.get(..6)
.is_some_and(|q| q.eq_ignore_ascii_case(b"SELECT"))
{
let mut rows = store.sql_query::<Rows>(&query, arguments).await?;
Ok(match rows.rows.len().cmp(&1) {
Ordering::Equal => {
let mut row = rows.rows.pop().unwrap().values;
match row.len().cmp(&1) {
Ordering::Equal if !matches!(row.first(), Some(Value::Null)) => {
row.pop().map(into_sieve_value).unwrap()
}
Ordering::Less => Variable::default(),
_ => Variable::Array(
row.into_iter()
.map(into_sieve_value)
.collect::<Vec<_>>()
.into(),
),
}
}
Ordering::Less => Variable::default(),
Ordering::Greater => rows
.rows
.into_iter()
.map(|r| {
Variable::Array(
r.values
.into_iter()
.map(into_sieve_value)
.collect::<Vec<_>>()
.into(),
)
})
.collect::<Vec<_>>()
.into(),
})
} else {
Ok(store
.sql_query::<usize>(&query, arguments)
.await
.is_ok()
.into())
}
}
+78
View File
@@ -0,0 +1,78 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use nlp::tokenizers::types::{TokenType, TypesTokenizer};
use sieve::{FunctionMap, runtime::Variable};
use crate::scripts::functions::{ApplyString, text::tokenize_words};
use super::PluginContext;
pub fn register_tokenize(plugin_id: u32, fnc_map: &mut FunctionMap) {
fnc_map.set_external_function("tokenize", plugin_id, 2);
}
pub fn register_domain_part(plugin_id: u32, fnc_map: &mut FunctionMap) {
fnc_map.set_external_function("domain_part", plugin_id, 2);
}
pub fn exec_tokenize(ctx: PluginContext<'_>) -> trc::Result<Variable> {
let mut v = ctx.arguments;
let (urls, urls_without_scheme, emails) = match v[1].to_string().as_ref() {
"words" => return Ok(tokenize_words(&v[0])),
"uri" | "url" => (true, true, true),
"uri_strict" | "url_strict" => (true, false, false),
"email" => (false, false, true),
_ => return Ok(Variable::default()),
};
Ok(match v.remove(0) {
v @ (Variable::String(_) | Variable::Array(_)) => {
TypesTokenizer::new(v.to_string().as_ref())
.tokenize_numbers(false)
.tokenize_urls(urls)
.tokenize_urls_without_scheme(urls_without_scheme)
.tokenize_emails(emails)
.filter_map(|t| match t.word {
TokenType::Url(text) if urls => Variable::from(text.to_string()).into(),
TokenType::UrlNoScheme(text) if urls_without_scheme => {
Variable::from(format!("https://{text}")).into()
}
TokenType::Email(text) if emails => Variable::from(text.to_string()).into(),
_ => None,
})
.collect::<Vec<_>>()
.into()
}
v => v,
})
}
enum DomainPart {
Sld,
Tld,
Host,
}
pub fn exec_domain_part(ctx: PluginContext<'_>) -> trc::Result<Variable> {
let v = ctx.arguments;
let part = match v[1].to_string().as_ref() {
"sld" => DomainPart::Sld,
"tld" => DomainPart::Tld,
"host" => DomainPart::Host,
_ => return Ok(Variable::default()),
};
Ok(v[0].transform(|domain| {
match part {
DomainPart::Sld => psl::domain_str(domain),
DomainPart::Tld => domain.rsplit_once('.').map(|(_, tld)| tld),
DomainPart::Host => domain.split_once('.').map(|(host, _)| host),
}
.map(Variable::from)
.unwrap_or_default()
}))
}