/* * Copyright (c) 2020-2023, Stalwart Labs Ltd. * * This file is part of the Stalwart Sieve Interpreter. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * in the LICENSE file at the top-level directory of this distribution. * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * You can be released from the requirements of the AGPLv3 license by * purchasing a commercial license. Please contact licensing@stalw.art * for more details. */ pub mod actions; pub mod context; pub mod eval; pub mod expression; pub mod serialize; pub mod tests; pub mod variables; pub(self) use std::iter::FromIterator; pub(self) use std::iter::IntoIterator; use std::{borrow::Cow, fmt::Display, ops::Deref, sync::Arc}; use ahash::{AHashMap, AHashSet}; use mail_parser::{Encoding, HeaderName, Message, MessageParser, MessagePart, PartType}; use crate::sieve::{ compiler::{ grammar::{Capability, Invalid}, Number, Regex, VariableType, }, Context, Function, FunctionMap, Input, Metadata, PluginArgument, Runtime, Script, SetVariable, Sieve, }; use self::eval::ToString; #[derive(Debug, Clone)] pub enum Variable<'x> { String(String), StringRef(&'x str), Integer(i64), Float(f64), Array(Vec>), ArrayRef(&'x Vec>), } #[derive(Debug)] pub enum RuntimeError { TooManyIncludes, InvalidInstruction(Invalid), ScriptErrorMessage(String), CapabilityNotAllowed(Capability), CapabilityNotSupported(String), CPULimitReached, } impl<'x> Default for Variable<'x> { fn default() -> Self { Variable::StringRef("") } } impl<'x> Variable<'x> { pub fn into_cow(self) -> Cow<'x, str> { match self { Variable::String(s) => Cow::Owned(s), Variable::StringRef(s) => Cow::Borrowed(s), Variable::Integer(n) => Cow::Owned(n.to_string()), Variable::Float(n) => Cow::Owned(n.to_string()), Variable::Array(l) => Cow::Owned(l.to_string()), Variable::ArrayRef(l) => Cow::Owned(l.to_string()), } } pub fn to_cow<'y: 'x>(&'y self) -> Cow<'x, str> { match self { Variable::String(s) => Cow::Borrowed(s.as_str()), Variable::StringRef(s) => Cow::Borrowed(*s), Variable::Integer(n) => Cow::Owned(n.to_string()), Variable::Float(n) => Cow::Owned(n.to_string()), Variable::Array(l) => Cow::Owned(l.to_string()), Variable::ArrayRef(l) => Cow::Owned(l.to_string()), } } pub fn into_string(self) -> String { match self { Variable::String(s) => s, Variable::StringRef(s) => s.to_string(), Variable::Integer(n) => n.to_string(), Variable::Float(n) => n.to_string(), Variable::Array(l) => l.to_string(), Variable::ArrayRef(l) => l.to_string(), } } pub fn to_number(&self) -> Number { self.to_number_checked() .unwrap_or(Number::Float(f64::INFINITY)) } pub fn to_number_checked(&self) -> Option { let s = match self { Variable::Integer(n) => return Number::Integer(*n).into(), Variable::Float(n) => return Number::Float(*n).into(), Variable::String(s) if !s.is_empty() => s.as_str(), Variable::StringRef(s) if !s.is_empty() => *s, _ => return None, }; if !s.contains('.') { s.parse::().map(Number::Integer).ok() } else { s.parse::().map(Number::Float).ok() } } pub fn to_integer(&self) -> i64 { match self { Variable::Integer(n) => *n, Variable::Float(n) => *n as i64, Variable::String(s) if !s.is_empty() => s.parse::().unwrap_or(0), Variable::StringRef(s) if !s.is_empty() => s.parse::().unwrap_or(0), _ => 0, } } pub fn len(&self) -> usize { match self { Variable::String(s) => s.len(), Variable::StringRef(s) => s.len(), Variable::Integer(_) | Variable::Float(_) => 2, Variable::Array(l) => l.iter().map(|v| v.len() + 2).sum(), Variable::ArrayRef(l) => l.iter().map(|v| v.len() + 2).sum(), } } pub fn is_empty(&self) -> bool { match self { Variable::String(s) => s.is_empty(), Variable::StringRef(s) => s.is_empty(), _ => false, } } pub fn to_owned(&self) -> Variable<'static> { match self { Variable::String(s) => Variable::String(s.to_string()), Variable::StringRef(s) => Variable::String(s.to_string()), Variable::Integer(n) => Variable::Integer(*n), Variable::Float(n) => Variable::Float(*n), Variable::Array(l) => Variable::Array(l.iter().map(Variable::to_owned).collect()), Variable::ArrayRef(l) => Variable::Array(l.iter().map(Variable::to_owned).collect()), } } pub fn into_owned(self) -> Variable<'static> { match self { Variable::String(s) => Variable::String(s), Variable::StringRef(s) => Variable::String(s.to_string()), Variable::Integer(n) => Variable::Integer(n), Variable::Float(n) => Variable::Float(n), Variable::Array(l) => { Variable::Array(l.into_iter().map(Variable::into_owned).collect()) } Variable::ArrayRef(l) => Variable::Array(l.iter().map(Variable::to_owned).collect()), } } pub fn as_ref<'y: 'x>(&'y self) -> Variable<'x> { match self { Variable::String(s) => Variable::StringRef(s.as_str()), Variable::StringRef(s) => Variable::StringRef(s), Variable::Integer(n) => Variable::Integer(*n), Variable::Float(n) => Variable::Float(*n), Variable::Array(l) => Variable::ArrayRef(l), Variable::ArrayRef(l) => Variable::ArrayRef(l), } } } impl<'x> From<&'x str> for Variable<'x> { fn from(s: &'x str) -> Self { Variable::StringRef(s) } } impl From for Variable<'_> { fn from(s: String) -> Self { Variable::String(s) } } impl<'x> From<&'x String> for Variable<'x> { fn from(s: &'x String) -> Self { Variable::StringRef(s.as_str()) } } impl<'x> From> for Variable<'x> { fn from(s: Cow<'x, str>) -> Self { match s { Cow::Borrowed(s) => Variable::StringRef(s), Cow::Owned(s) => Variable::String(s), } } } impl<'x> From>> for Variable<'x> { fn from(l: Vec>) -> Self { Variable::Array(l) } } impl From for Variable<'_> { fn from(n: Number) -> Self { match n { Number::Integer(n) => Variable::Integer(n), Number::Float(n) => Variable::Float(n), } } } impl From for Variable<'_> { fn from(n: usize) -> Self { Variable::Integer(n as i64) } } impl From for Variable<'_> { fn from(n: i64) -> Self { Variable::Integer(n) } } impl From for Variable<'_> { fn from(n: u64) -> Self { Variable::Integer(n as i64) } } impl From for Variable<'_> { fn from(n: f64) -> Self { Variable::Float(n) } } impl From for Variable<'_> { fn from(n: i32) -> Self { Variable::Integer(n as i64) } } impl From for Variable<'_> { fn from(b: bool) -> Self { Variable::Integer(i64::from(b)) } } impl PartialEq for Number { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Integer(a), Self::Integer(b)) => a == b, (Self::Float(a), Self::Float(b)) => a == b, (Self::Integer(a), Self::Float(b)) => (*a as f64) == *b, (Self::Float(a), Self::Integer(b)) => *a == (*b as f64), } } } impl Eq for Number {} impl PartialOrd for Number { fn partial_cmp(&self, other: &Self) -> Option { let (a, b) = match (self, other) { (Number::Integer(a), Number::Integer(b)) => return a.partial_cmp(b), (Number::Float(a), Number::Float(b)) => (*a, *b), (Number::Integer(a), Number::Float(b)) => (*a as f64, *b), (Number::Float(a), Number::Integer(b)) => (*a, *b as f64), }; a.partial_cmp(&b) } } impl<'x> self::eval::ToString for Vec> { fn to_string(&self) -> String { let mut result = String::with_capacity(self.len() * 10); for item in self { if !result.is_empty() { result.push_str("\r\n"); } match item { Variable::String(v) => result.push_str(v), Variable::StringRef(v) => result.push_str(v), Variable::Integer(v) => result.push_str(&v.to_string()), Variable::Float(v) => result.push_str(&v.to_string()), Variable::Array(_) | Variable::ArrayRef(_) => {} } } result } } impl PluginArgument { pub fn unwrap_string(self) -> Option { match self { PluginArgument::Text(s) => s.into(), PluginArgument::Number(n) => n.to_string().into(), _ => None, } } pub fn unwrap_number(self) -> Option { match self { PluginArgument::Number(n) => n.into(), _ => None, } } pub fn unwrap_regex(self) -> Option { match self { PluginArgument::Regex(r) => r.into(), _ => None, } } pub fn unwrap_array(self) -> Option> { match self { PluginArgument::Array(a) => a.into(), _ => None, } } pub fn unwrap_variable(self) -> Option { match self { PluginArgument::Variable(v) => v.into(), _ => None, } } pub fn unwrap_string_array(self) -> Option> { match self { PluginArgument::Array(a) => a .into_iter() .filter_map(Self::unwrap_string) .collect::>() .into(), _ => None, } } pub fn unwrap_number_array(self) -> Option> { match self { PluginArgument::Array(a) => a .into_iter() .filter_map(Self::unwrap_number) .collect::>() .into(), _ => None, } } pub fn unwrap_regex_array(self) -> Option> { match self { PluginArgument::Array(a) => a .into_iter() .filter_map(Self::unwrap_regex) .collect::>() .into(), _ => None, } } pub fn unwrap_variable_array(self) -> Option> { match self { PluginArgument::Array(a) => a .into_iter() .filter_map(Self::unwrap_variable) .collect::>() .into(), _ => None, } } } impl Runtime { pub fn new() -> Self { #[allow(unused_mut)] let mut allowed_capabilities = AHashSet::from_iter(Capability::all().iter().cloned()); #[cfg(test)] allowed_capabilities.insert(Capability::Other("vnd.stalwart.testsuite".to_string())); Runtime { allowed_capabilities, environment: AHashMap::from_iter([ ("name".into(), "Stalwart Sieve".into()), ("version".into(), env!("CARGO_PKG_VERSION").into()), ]), metadata: Vec::new(), include_scripts: AHashMap::new(), max_nested_includes: 3, cpu_limit: 5000, max_variable_size: 4096, max_redirects: 1, max_received_headers: 10, protected_headers: vec![ HeaderName::Other("Original-Subject".into()), HeaderName::Other("Original-From".into()), ], valid_notification_uris: AHashSet::new(), valid_ext_lists: AHashSet::new(), vacation_use_orig_rcpt: false, vacation_default_subject: "Automated reply".into(), vacation_subject_prefix: "Auto: ".into(), max_header_size: 1024, max_out_messages: 3, default_vacation_expiry: 30 * 86400, default_duplicate_expiry: 7 * 86400, local_hostname: "localhost".into(), functions: Vec::new(), } } pub fn set_cpu_limit(&mut self, size: usize) { self.cpu_limit = size; } pub fn with_cpu_limit(mut self, size: usize) -> Self { self.cpu_limit = size; self } pub fn set_max_nested_includes(&mut self, size: usize) { self.max_nested_includes = size; } pub fn with_max_nested_includes(mut self, size: usize) -> Self { self.max_nested_includes = size; self } pub fn set_max_redirects(&mut self, size: usize) { self.max_redirects = size; } pub fn with_max_redirects(mut self, size: usize) -> Self { self.max_redirects = size; self } pub fn set_max_out_messages(&mut self, size: usize) { self.max_out_messages = size; } pub fn with_max_out_messages(mut self, size: usize) -> Self { self.max_out_messages = size; self } pub fn set_max_received_headers(&mut self, size: usize) { self.max_received_headers = size; } pub fn with_max_received_headers(mut self, size: usize) -> Self { self.max_received_headers = size; self } pub fn set_max_variable_size(&mut self, size: usize) { self.max_variable_size = size; } pub fn with_max_variable_size(mut self, size: usize) -> Self { self.max_variable_size = size; self } pub fn set_max_header_size(&mut self, size: usize) { self.max_header_size = size; } pub fn with_max_header_size(mut self, size: usize) -> Self { self.max_header_size = size; self } pub fn set_default_vacation_expiry(&mut self, expiry: u64) { self.default_vacation_expiry = expiry; } pub fn with_default_vacation_expiry(mut self, expiry: u64) -> Self { self.default_vacation_expiry = expiry; self } pub fn set_default_duplicate_expiry(&mut self, expiry: u64) { self.default_duplicate_expiry = expiry; } pub fn with_default_duplicate_expiry(mut self, expiry: u64) -> Self { self.default_duplicate_expiry = expiry; self } pub fn set_capability(&mut self, capability: impl Into) { self.allowed_capabilities.insert(capability.into()); } pub fn with_capability(mut self, capability: impl Into) -> Self { self.set_capability(capability); self } pub fn unset_capability(&mut self, capability: impl Into) { self.allowed_capabilities.remove(&capability.into()); } pub fn without_capability(mut self, capability: impl Into) -> Self { self.unset_capability(capability); self } pub fn without_capabilities( mut self, capabilities: impl IntoIterator>, ) -> Self { for capability in capabilities { self.allowed_capabilities.remove(&capability.into()); } self } pub fn set_protected_header(&mut self, header_name: impl Into>) { if let Some(header_name) = HeaderName::parse(header_name) { self.protected_headers.push(header_name); } } pub fn with_protected_header(mut self, header_name: impl Into>) -> Self { self.set_protected_header(header_name); self } pub fn with_protected_headers( mut self, header_names: impl IntoIterator>>, ) -> Self { self.protected_headers = header_names .into_iter() .filter_map(HeaderName::parse) .collect(); self } pub fn set_env_variable( &mut self, name: impl Into>, value: impl Into>, ) { self.environment.insert(name.into(), value.into()); } pub fn with_env_variable( mut self, name: impl Into>, value: impl Into>, ) -> Self { self.set_env_variable(name.into(), value.into()); self } pub fn set_medatata( &mut self, name: impl Into>, value: impl Into>, ) { self.metadata.push((name.into(), value.into())); } pub fn with_metadata( mut self, name: impl Into>, value: impl Into>, ) -> Self { self.set_medatata(name, value); self } pub fn set_valid_notification_uri(&mut self, uri: impl Into>) { self.valid_notification_uris.insert(uri.into()); } pub fn with_valid_notification_uri(mut self, uri: impl Into>) -> Self { self.valid_notification_uris.insert(uri.into()); self } pub fn with_valid_notification_uris( mut self, uris: impl IntoIterator>>, ) -> Self { self.valid_notification_uris = uris.into_iter().map(Into::into).collect(); self } pub fn set_valid_ext_list(&mut self, name: impl Into>) { self.valid_ext_lists.insert(name.into()); } pub fn with_valid_ext_list(mut self, name: impl Into>) -> Self { self.set_valid_ext_list(name); self } pub fn set_vacation_use_orig_rcpt(&mut self, value: bool) { self.vacation_use_orig_rcpt = value; } pub fn with_valid_ext_lists( mut self, lists: impl IntoIterator>>, ) -> Self { self.valid_ext_lists = lists.into_iter().map(Into::into).collect(); self } pub fn with_vacation_use_orig_rcpt(mut self, value: bool) -> Self { self.set_vacation_use_orig_rcpt(value); self } pub fn set_vacation_default_subject(&mut self, value: impl Into>) { self.vacation_default_subject = value.into(); } pub fn with_vacation_default_subject(mut self, value: impl Into>) -> Self { self.set_vacation_default_subject(value); self } pub fn set_vacation_subject_prefix(&mut self, value: impl Into>) { self.vacation_subject_prefix = value.into(); } pub fn with_vacation_subject_prefix(mut self, value: impl Into>) -> Self { self.set_vacation_subject_prefix(value); self } pub fn set_local_hostname(&mut self, value: impl Into>) { self.local_hostname = value.into(); } pub fn with_local_hostname(mut self, value: impl Into>) -> Self { self.set_local_hostname(value); self } pub fn with_functions(mut self, fnc_map: &mut FunctionMap) -> Self { self.functions = std::mem::take(&mut fnc_map.functions); self } pub fn set_functions(&mut self, fnc_map: &mut FunctionMap) { self.functions = std::mem::take(&mut fnc_map.functions); } pub fn filter<'z: 'x, 'x>(&'z self, raw_message: &'x [u8]) -> Context<'x> { Context::new( self, MessageParser::new() .parse(raw_message) .unwrap_or_else(|| Message { parts: vec![MessagePart { headers: vec![], is_encoding_problem: false, body: PartType::Text("".into()), encoding: Encoding::None, offset_header: 0, offset_body: 0, offset_end: 0, }], raw_message: b""[..].into(), ..Default::default() }), ) } pub fn filter_parsed<'z: 'x, 'x>(&'z self, message: Message<'x>) -> Context<'x> { Context::new(self, message) } } impl FunctionMap { pub fn new() -> Self { Self::default() } pub fn with_function(self, name: impl Into, fnc: Function) -> Self { self.with_function_args(name, fnc, 1) } pub fn with_function_no_args(self, name: impl Into, fnc: Function) -> Self { self.with_function_args(name, fnc, 0) } pub fn with_function_args( mut self, name: impl Into, fnc: Function, num_args: u32, ) -> Self { self.map .insert(name.into(), (self.functions.len() as u32, num_args)); self.functions.push(fnc); self } } impl Default for Runtime { fn default() -> Self { Self::new() } } impl Input { pub fn script(name: impl Into