Skip to main content

sieve/runtime/
mod.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
5 */
6
7pub mod actions;
8mod arena;
9pub mod context;
10pub mod eval;
11pub mod expression;
12pub mod handler;
13pub mod tests;
14pub mod variable;
15pub mod variables;
16
17pub use arena::Arena;
18pub use handler::{
19    Action, Handler, Input, Mailbox, MessageSource, Recipient, Reply, Script, Status,
20};
21pub use variable::Variable;
22
23use crate::{
24    ExternalId, Function, FunctionMap, Metadata, Runtime, Sieve,
25    bytecode::Corrupt,
26    compiler::{
27        Number,
28        grammar::{Capability, expr::parser::ID_EXTERNAL},
29    },
30};
31use ahash::{AHashMap, AHashSet};
32use mail_parser::HeaderName;
33use mail_parser::{Encoding, Message, MessageParser, MessagePart, PartType};
34use std::borrow::Cow;
35
36use crate::Context;
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum RuntimeError {
40    TooManyIncludes,
41    ScriptNotFound(String),
42    InvalidInstruction {
43        name: String,
44        line_num: u32,
45        line_pos: u32,
46    },
47    ScriptErrorMessage(String),
48    CapabilityNotAllowed(Capability),
49    CapabilityNotSupported(String),
50    CPULimitReached,
51    MemoryLimitReached,
52    InvalidBytecode,
53    AwaitingInput,
54}
55
56impl From<Corrupt> for RuntimeError {
57    fn from(_: Corrupt) -> Self {
58        RuntimeError::InvalidBytecode
59    }
60}
61
62impl Number {
63    pub fn is_non_zero(&self) -> bool {
64        match self {
65            Number::Integer(n) => *n != 0,
66            Number::Float(n) => *n != 0.0,
67        }
68    }
69}
70
71impl Default for Number {
72    fn default() -> Self {
73        Number::Integer(0)
74    }
75}
76
77impl From<bool> for Number {
78    #[inline(always)]
79    fn from(b: bool) -> Self {
80        Number::Integer(i64::from(b))
81    }
82}
83
84impl From<i64> for Number {
85    #[inline(always)]
86    fn from(n: i64) -> Self {
87        Number::Integer(n)
88    }
89}
90
91impl From<f64> for Number {
92    #[inline(always)]
93    fn from(n: f64) -> Self {
94        Number::Float(n)
95    }
96}
97
98impl From<i32> for Number {
99    #[inline(always)]
100    fn from(n: i32) -> Self {
101        Number::Integer(n as i64)
102    }
103}
104
105impl PartialEq for Number {
106    fn eq(&self, other: &Self) -> bool {
107        match (self, other) {
108            (Self::Integer(a), Self::Integer(b)) => a == b,
109            (Self::Float(a), Self::Float(b)) => a == b,
110            (Self::Integer(a), Self::Float(b)) => (*a as f64) == *b,
111            (Self::Float(a), Self::Integer(b)) => *a == (*b as f64),
112        }
113    }
114}
115
116impl Eq for Number {}
117
118impl PartialOrd for Number {
119    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
120        let (a, b) = match (self, other) {
121            (Number::Integer(a), Number::Integer(b)) => return a.partial_cmp(b),
122            (Number::Float(a), Number::Float(b)) => (*a, *b),
123            (Number::Integer(a), Number::Float(b)) => (*a as f64, *b),
124            (Number::Float(a), Number::Integer(b)) => (*a, *b as f64),
125        };
126        a.partial_cmp(&b)
127    }
128}
129
130impl Runtime {
131    pub fn filter<'z: 'x, 'x>(
132        &'z self,
133        raw_message: &'x [u8],
134        script: &'x Sieve<'x>,
135        arena: &'x mut Arena,
136    ) -> Context<'x> {
137        Context::new(
138            self,
139            MessageParser::new()
140                .parse(raw_message)
141                .unwrap_or_else(|| Message {
142                    parts: vec![MessagePart {
143                        headers: vec![],
144                        is_encoding_problem: false,
145                        body: PartType::Text("".into()),
146                        encoding: Encoding::None,
147                        offset_header: 0,
148                        offset_body: 0,
149                        offset_end: 0,
150                    }],
151                    raw_message: b""[..].into(),
152                    ..Default::default()
153                }),
154            script,
155            arena,
156        )
157    }
158
159    pub fn filter_parsed<'z: 'x, 'x>(
160        &'z self,
161        message: Message<'x>,
162        script: &'x Sieve<'x>,
163        arena: &'x mut Arena,
164    ) -> Context<'x> {
165        Context::new(self, message, script, arena)
166    }
167}
168
169impl Default for Runtime {
170    fn default() -> Self {
171        Self::new()
172    }
173}
174
175impl Runtime {
176    pub fn new() -> Self {
177        #[allow(unused_mut)]
178        let mut allowed_capabilities = AHashSet::from_iter(Capability::all().iter().cloned());
179
180        #[cfg(test)]
181        allowed_capabilities.insert(Capability::Other("vnd.stalwart.testsuite".to_string()));
182
183        Runtime {
184            allowed_capabilities,
185            environment: AHashMap::from_iter([
186                ("name".into(), "Stalwart Sieve".into()),
187                ("version".into(), env!("CARGO_PKG_VERSION").into()),
188            ]),
189            metadata: Vec::new(),
190            include_scripts: AHashMap::new(),
191            max_nested_includes: 3,
192            cpu_limit: 5000,
193            memory_limit: 32 * 1024 * 1024,
194            max_variable_size: 4096,
195            max_redirects: 1,
196            max_received_headers: 10,
197            protected_headers: vec![
198                HeaderName::Other("Original-Subject".into()),
199                HeaderName::Other("Original-From".into()),
200            ],
201            valid_notification_uris: AHashSet::new(),
202            valid_ext_lists: AHashSet::new(),
203            vacation_use_orig_rcpt: false,
204            vacation_default_subject: "Automated reply".into(),
205            vacation_subject_prefix: "Auto: ".into(),
206            max_header_size: 1024,
207            max_out_messages: 3,
208            default_vacation_expiry: 30 * 86400,
209            default_duplicate_expiry: 7 * 86400,
210            local_hostname: "localhost".into(),
211            functions: Vec::new(),
212        }
213    }
214
215    pub fn set_cpu_limit(&mut self, size: usize) {
216        self.cpu_limit = size;
217    }
218
219    pub fn with_cpu_limit(mut self, size: usize) -> Self {
220        self.cpu_limit = size;
221        self
222    }
223
224    pub fn set_memory_limit(&mut self, size: usize) {
225        self.memory_limit = size;
226    }
227
228    pub fn with_memory_limit(mut self, size: usize) -> Self {
229        self.memory_limit = size;
230        self
231    }
232
233    pub fn set_max_nested_includes(&mut self, size: usize) {
234        self.max_nested_includes = size;
235    }
236
237    pub fn with_max_nested_includes(mut self, size: usize) -> Self {
238        self.max_nested_includes = size;
239        self
240    }
241
242    pub fn set_max_redirects(&mut self, size: usize) {
243        self.max_redirects = size;
244    }
245
246    pub fn with_max_redirects(mut self, size: usize) -> Self {
247        self.max_redirects = size;
248        self
249    }
250
251    pub fn set_max_out_messages(&mut self, size: usize) {
252        self.max_out_messages = size;
253    }
254
255    pub fn with_max_out_messages(mut self, size: usize) -> Self {
256        self.max_out_messages = size;
257        self
258    }
259
260    pub fn set_max_received_headers(&mut self, size: usize) {
261        self.max_received_headers = size;
262    }
263
264    pub fn with_max_received_headers(mut self, size: usize) -> Self {
265        self.max_received_headers = size;
266        self
267    }
268
269    pub fn set_max_variable_size(&mut self, size: usize) {
270        self.max_variable_size = size;
271    }
272
273    pub fn with_max_variable_size(mut self, size: usize) -> Self {
274        self.max_variable_size = size;
275        self
276    }
277
278    pub fn set_max_header_size(&mut self, size: usize) {
279        self.max_header_size = size;
280    }
281
282    pub fn with_max_header_size(mut self, size: usize) -> Self {
283        self.max_header_size = size;
284        self
285    }
286
287    pub fn set_default_vacation_expiry(&mut self, expiry: u64) {
288        self.default_vacation_expiry = expiry;
289    }
290
291    pub fn with_default_vacation_expiry(mut self, expiry: u64) -> Self {
292        self.default_vacation_expiry = expiry;
293        self
294    }
295
296    pub fn set_default_duplicate_expiry(&mut self, expiry: u64) {
297        self.default_duplicate_expiry = expiry;
298    }
299
300    pub fn with_default_duplicate_expiry(mut self, expiry: u64) -> Self {
301        self.default_duplicate_expiry = expiry;
302        self
303    }
304
305    pub fn set_capability(&mut self, capability: impl Into<Capability>) {
306        self.allowed_capabilities.insert(capability.into());
307    }
308
309    pub fn with_capability(mut self, capability: impl Into<Capability>) -> Self {
310        self.set_capability(capability);
311        self
312    }
313
314    pub fn unset_capability(&mut self, capability: impl Into<Capability>) {
315        self.allowed_capabilities.remove(&capability.into());
316    }
317
318    pub fn without_capability(mut self, capability: impl Into<Capability>) -> Self {
319        self.unset_capability(capability);
320        self
321    }
322
323    pub fn without_capabilities(
324        mut self,
325        capabilities: impl IntoIterator<Item = impl Into<Capability>>,
326    ) -> Self {
327        for capability in capabilities {
328            self.allowed_capabilities.remove(&capability.into());
329        }
330        self
331    }
332
333    pub fn set_protected_header(&mut self, header_name: impl Into<Cow<'static, str>>) {
334        if let Some(header_name) = HeaderName::parse(header_name) {
335            self.protected_headers.push(header_name);
336        }
337    }
338
339    pub fn with_protected_header(mut self, header_name: impl Into<Cow<'static, str>>) -> Self {
340        self.set_protected_header(header_name);
341        self
342    }
343
344    pub fn with_protected_headers(
345        mut self,
346        header_names: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
347    ) -> Self {
348        self.protected_headers = header_names
349            .into_iter()
350            .filter_map(HeaderName::parse)
351            .collect();
352        self
353    }
354
355    pub fn set_env_variable(
356        &mut self,
357        name: impl Into<Cow<'static, str>>,
358        value: impl Into<Variable<'static>>,
359    ) {
360        self.environment.insert(name.into(), value.into());
361    }
362
363    pub fn with_env_variable(
364        mut self,
365        name: impl Into<Cow<'static, str>>,
366        value: impl Into<Variable<'static>>,
367    ) -> Self {
368        self.set_env_variable(name, value);
369        self
370    }
371
372    pub fn set_medatata(
373        &mut self,
374        name: impl Into<Metadata<String>>,
375        value: impl Into<Cow<'static, str>>,
376    ) {
377        self.metadata.push((name.into(), value.into()));
378    }
379
380    pub fn with_metadata(
381        mut self,
382        name: impl Into<Metadata<String>>,
383        value: impl Into<Cow<'static, str>>,
384    ) -> Self {
385        self.set_medatata(name, value);
386        self
387    }
388
389    pub fn set_valid_notification_uri(&mut self, uri: impl Into<Cow<'static, str>>) {
390        self.valid_notification_uris.insert(uri.into());
391    }
392
393    pub fn with_valid_notification_uri(mut self, uri: impl Into<Cow<'static, str>>) -> Self {
394        self.valid_notification_uris.insert(uri.into());
395        self
396    }
397
398    pub fn with_valid_notification_uris(
399        mut self,
400        uris: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
401    ) -> Self {
402        self.valid_notification_uris = uris.into_iter().map(Into::into).collect();
403        self
404    }
405
406    pub fn set_valid_ext_list(&mut self, name: impl Into<Cow<'static, str>>) {
407        self.valid_ext_lists.insert(name.into());
408    }
409
410    pub fn with_valid_ext_list(mut self, name: impl Into<Cow<'static, str>>) -> Self {
411        self.set_valid_ext_list(name);
412        self
413    }
414
415    pub fn set_vacation_use_orig_rcpt(&mut self, value: bool) {
416        self.vacation_use_orig_rcpt = value;
417    }
418
419    pub fn with_valid_ext_lists(
420        mut self,
421        lists: impl IntoIterator<Item = impl Into<Cow<'static, str>>>,
422    ) -> Self {
423        self.valid_ext_lists = lists.into_iter().map(Into::into).collect();
424        self
425    }
426
427    pub fn with_vacation_use_orig_rcpt(mut self, value: bool) -> Self {
428        self.set_vacation_use_orig_rcpt(value);
429        self
430    }
431
432    pub fn set_vacation_default_subject(&mut self, value: impl Into<Cow<'static, str>>) {
433        self.vacation_default_subject = value.into();
434    }
435
436    pub fn with_vacation_default_subject(mut self, value: impl Into<Cow<'static, str>>) -> Self {
437        self.set_vacation_default_subject(value);
438        self
439    }
440
441    pub fn set_vacation_subject_prefix(&mut self, value: impl Into<Cow<'static, str>>) {
442        self.vacation_subject_prefix = value.into();
443    }
444
445    pub fn with_vacation_subject_prefix(mut self, value: impl Into<Cow<'static, str>>) -> Self {
446        self.set_vacation_subject_prefix(value);
447        self
448    }
449
450    pub fn set_local_hostname(&mut self, value: impl Into<Cow<'static, str>>) {
451        self.local_hostname = value.into();
452    }
453
454    pub fn with_local_hostname(mut self, value: impl Into<Cow<'static, str>>) -> Self {
455        self.set_local_hostname(value);
456        self
457    }
458
459    pub fn with_functions(mut self, fnc_map: &mut FunctionMap) -> Self {
460        self.functions = std::mem::take(&mut fnc_map.functions);
461        self
462    }
463
464    pub fn set_functions(&mut self, fnc_map: &mut FunctionMap) {
465        self.functions = std::mem::take(&mut fnc_map.functions);
466    }
467
468    pub fn set_include_script(&mut self, name: impl Into<String>, script: Sieve<'static>) {
469        self.include_scripts.insert(name.into(), script);
470    }
471
472    pub fn with_include_script(mut self, name: impl Into<String>, script: Sieve<'static>) -> Self {
473        self.set_include_script(name, script);
474        self
475    }
476
477    pub fn include_script(&self, name: &str) -> Option<&Sieve<'static>> {
478        self.include_scripts.get(name)
479    }
480}
481
482impl FunctionMap {
483    pub fn new() -> Self {
484        FunctionMap {
485            map: Default::default(),
486            functions: Default::default(),
487        }
488    }
489
490    pub fn with_function(self, name: impl Into<String>, fnc: Function) -> Self {
491        self.with_function_args(name, fnc, 1)
492    }
493
494    pub fn with_function_no_args(self, name: impl Into<String>, fnc: Function) -> Self {
495        self.with_function_args(name, fnc, 0)
496    }
497
498    pub fn with_function_args(
499        mut self,
500        name: impl Into<String>,
501        fnc: Function,
502        num_args: u32,
503    ) -> Self {
504        self.map
505            .insert(name.into(), (self.functions.len() as u32, num_args));
506        self.functions.push(fnc);
507        self
508    }
509
510    pub fn with_external_function(
511        mut self,
512        name: impl Into<String>,
513        id: ExternalId,
514        num_args: u32,
515    ) -> Self {
516        self.set_external_function(name, id, num_args);
517        self
518    }
519
520    pub fn set_external_function(
521        &mut self,
522        name: impl Into<String>,
523        id: ExternalId,
524        num_args: u32,
525    ) {
526        self.map.insert(name.into(), (ID_EXTERNAL - id, num_args));
527    }
528}
529
530impl<T> Metadata<T> {
531    pub fn server(annotation: impl Into<T>) -> Self {
532        Metadata::Server {
533            annotation: annotation.into(),
534        }
535    }
536
537    pub fn mailbox(name: impl Into<T>, annotation: impl Into<T>) -> Self {
538        Metadata::Mailbox {
539            name: name.into(),
540            annotation: annotation.into(),
541        }
542    }
543}
544
545impl From<String> for Metadata<String> {
546    fn from(annotation: String) -> Self {
547        Metadata::Server { annotation }
548    }
549}
550
551impl From<&'_ str> for Metadata<String> {
552    fn from(annotation: &'_ str) -> Self {
553        Metadata::Server {
554            annotation: annotation.to_string(),
555        }
556    }
557}
558
559impl From<(String, String)> for Metadata<String> {
560    fn from((name, annotation): (String, String)) -> Self {
561        Metadata::Mailbox { name, annotation }
562    }
563}
564
565impl From<(&'_ str, &'_ str)> for Metadata<String> {
566    fn from((name, annotation): (&'_ str, &'_ str)) -> Self {
567        Metadata::Mailbox {
568            name: name.to_string(),
569            annotation: annotation.to_string(),
570        }
571    }
572}