miltr_common/actions/
to_mta_only.rs

1use std::borrow::Cow;
2
3use bytes::{BufMut, BytesMut};
4use itertools::Itertools;
5
6use crate::decoding::Parsable;
7use crate::encoding::Writable;
8use crate::{error::STAGE_DECODING, NotEnoughData};
9use crate::{InvalidData, ProtocolError};
10use miltr_utils::ByteParsing;
11
12/// (Silently) discard this mail without forwarding it
13#[derive(Debug, Clone)]
14pub struct Discard;
15
16impl Discard {
17    const CODE: u8 = b'd';
18}
19
20impl Parsable for Discard {
21    const CODE: u8 = Self::CODE;
22
23    fn parse(_buffer: BytesMut) -> Result<Self, ProtocolError> {
24        Ok(Self)
25    }
26}
27
28impl Writable for Discard {
29    fn write(&self, _buffer: &mut BytesMut) {}
30
31    fn len(&self) -> usize {
32        0
33    }
34
35    fn code(&self) -> u8 {
36        Self::CODE
37    }
38
39    fn is_empty(&self) -> bool {
40        self.len() == 0
41    }
42}
43
44/// Reject this mail, informing the smtp client about it
45#[derive(Debug, Clone)]
46pub struct Reject;
47
48impl Reject {
49    const CODE: u8 = b'r';
50}
51
52impl Parsable for Reject {
53    const CODE: u8 = Self::CODE;
54
55    fn parse(_buffer: BytesMut) -> Result<Self, ProtocolError> {
56        Ok(Self)
57    }
58}
59
60impl Writable for Reject {
61    fn write(&self, _buffer: &mut BytesMut) {}
62
63    fn len(&self) -> usize {
64        0
65    }
66
67    fn code(&self) -> u8 {
68        Self::CODE
69    }
70
71    fn is_empty(&self) -> bool {
72        self.len() == 0
73    }
74}
75
76/// Return a tempfail code to the smtp client
77#[derive(Debug, Clone)]
78pub struct Tempfail;
79
80impl Tempfail {
81    const CODE: u8 = b't';
82}
83
84impl Parsable for Tempfail {
85    const CODE: u8 = Self::CODE;
86
87    fn parse(_buffer: BytesMut) -> Result<Self, ProtocolError> {
88        Ok(Self)
89    }
90}
91
92impl Writable for Tempfail {
93    fn write(&self, _buffer: &mut BytesMut) {}
94
95    fn len(&self) -> usize {
96        0
97    }
98
99    fn code(&self) -> u8 {
100        Self::CODE
101    }
102    fn is_empty(&self) -> bool {
103        self.len() == 0
104    }
105}
106
107/// Skip this mail processing
108#[derive(Debug, Clone)]
109pub struct Skip;
110
111impl Skip {
112    const CODE: u8 = b's';
113}
114
115impl Parsable for Skip {
116    const CODE: u8 = Self::CODE;
117
118    fn parse(_buffer: BytesMut) -> Result<Self, ProtocolError> {
119        Ok(Self)
120    }
121}
122
123impl Writable for Skip {
124    fn write(&self, _buffer: &mut BytesMut) {}
125
126    fn len(&self) -> usize {
127        0
128    }
129
130    fn code(&self) -> u8 {
131        Self::CODE
132    }
133
134    fn is_empty(&self) -> bool {
135        self.len() == 0
136    }
137}
138
139const REPLY_CODE_LENGTH: usize = 3;
140/// Return this status code to the smtp client
141#[derive(Debug, Clone)]
142pub struct Replycode {
143    rcode: RCode,
144    xcode: Option<XCode>,
145    message: BytesMut,
146}
147
148impl Replycode {
149    const CODE: u8 = b'y';
150
151    /// Create a Replycode
152    #[must_use]
153    #[allow(clippy::similar_names)]
154    pub fn new<R: Into<RCode>, X: Into<XCode>>(rcode: R, xcode: X, message: &str) -> Self {
155        let rcode = rcode.into();
156        let xcode = Some(xcode.into());
157
158        Self {
159            rcode,
160            xcode,
161            message: BytesMut::from(message.as_bytes()),
162        }
163    }
164
165    /// Create a Replycode without xcode
166    #[allow(clippy::similar_names)]
167    pub fn without_xcode<R: Into<RCode>>(rcode: R, message: &str) -> Self {
168        let rcode = rcode.into();
169
170        Self {
171            rcode,
172            xcode: None,
173            message: BytesMut::from(message.as_bytes()),
174        }
175    }
176
177    /// The message associated with this reply code
178    #[must_use]
179    pub fn message(&self) -> Cow<str> {
180        String::from_utf8_lossy(&self.message)
181    }
182
183    /// The smtp return code
184    #[must_use]
185    pub fn rcode(&self) -> &RCode {
186        &self.rcode
187    }
188
189    /// The smtp enhanced return code
190    #[must_use]
191    pub fn xcode(&self) -> &Option<XCode> {
192        &self.xcode
193    }
194}
195
196impl Parsable for Replycode {
197    const CODE: u8 = Self::CODE;
198
199    // rcode and xcode are just named that in the docs. Keeping it consistent.
200    #[allow(clippy::similar_names)]
201    fn parse(mut buffer: BytesMut) -> Result<Self, ProtocolError> {
202        #[allow(clippy::similar_names)]
203        let Some(rcode) = buffer.delimited(b' ') else {
204            return Err(NotEnoughData::new(
205                STAGE_DECODING,
206                "Replycode",
207                "Missing nullbyte delimiter after rcode",
208                1,
209                0,
210                buffer,
211            )
212            .into());
213        };
214        let rcode = RCode::parse(rcode)?;
215
216        let Some(raw_message) = buffer.delimited(0) else {
217            return Err(NotEnoughData::new(
218                STAGE_DECODING,
219                "Replycode",
220                "Missing nullbyte delimiter after message",
221                1,
222                0,
223                buffer,
224            )
225            .into());
226        };
227        let mut xcode = None;
228        let mut message = raw_message;
229
230        if let Some(pos) = message.iter().position(|c| *c == b' ') {
231            if let Ok(code) = XCode::parse(BytesMut::from(message[0..pos].as_ref())) {
232                xcode = Some(code);
233                message = BytesMut::from(&message[pos + 1..]);
234            }
235        }
236
237        Ok(Self {
238            rcode,
239            xcode,
240            message,
241        })
242    }
243}
244
245impl Writable for Replycode {
246    fn write(&self, buffer: &mut BytesMut) {
247        buffer.put_slice(self.rcode.as_bytes());
248        buffer.put_u8(b' ');
249        if let Some(ref xcode) = self.xcode {
250            buffer.put_slice(xcode.as_bytes());
251            buffer.put_u8(b' ');
252        }
253        buffer.put_slice(&self.message);
254        buffer.put_u8(0);
255    }
256
257    fn len(&self) -> usize {
258        self.rcode.len()
259            + 1
260            + self.xcode.as_ref().map_or(0, |code| code.len() + 1)
261            + self.message.len()
262            + 1
263    }
264
265    fn code(&self) -> u8 {
266        Self::CODE
267    }
268    fn is_empty(&self) -> bool {
269        self.len() == 0
270    }
271}
272
273#[derive(Debug, Clone)]
274pub struct XCode {
275    code: [u16; REPLY_CODE_LENGTH],
276    bytes: BytesMut,
277}
278
279impl From<[u16; REPLY_CODE_LENGTH]> for XCode {
280    fn from(code: [u16; REPLY_CODE_LENGTH]) -> Self {
281        Self::new(code)
282    }
283}
284
285impl XCode {
286    pub fn new(code: [u16; REPLY_CODE_LENGTH]) -> Self {
287        Self {
288            code,
289            bytes: BytesMut::from_iter(code.iter().map(ToString::to_string).join(".").as_bytes()),
290        }
291    }
292
293    fn parse(buffer: BytesMut) -> Result<Self, InvalidData> {
294        let mut positions = buffer.iter().positions(|&c| c == b'.');
295        let mut code: [u16; 3] = [0_u16; REPLY_CODE_LENGTH];
296
297        let mut start = 0;
298        for c_code in code.iter_mut().take(REPLY_CODE_LENGTH - 1) {
299            let Some(end) = positions.next() else {
300                return Err(InvalidData {
301                    msg: "missing '.' delimiter in code",
302                    offending_bytes: buffer,
303                });
304            };
305            let raw = &buffer[start..end];
306            let Ok(number) = String::from_utf8_lossy(raw).parse() else {
307                return Err(InvalidData {
308                    msg: "invalid u16 in code",
309                    offending_bytes: buffer,
310                });
311            };
312
313            *c_code = number;
314            start = end + 1;
315        }
316        let raw = &buffer[start..buffer.len()];
317        let Ok(number) = String::from_utf8_lossy(raw).parse() else {
318            return Err(InvalidData {
319                msg: "invalid u16 in code",
320                offending_bytes: buffer,
321            });
322        };
323
324        code[REPLY_CODE_LENGTH - 1] = number;
325
326        Ok(Self {
327            code,
328            bytes: buffer,
329        })
330    }
331
332    /// The status code
333    #[must_use]
334    pub fn code(&self) -> [u16; REPLY_CODE_LENGTH] {
335        self.code
336    }
337
338    fn as_bytes(&self) -> &[u8] {
339        &self.bytes
340    }
341
342    fn len(&self) -> usize {
343        self.bytes.len()
344    }
345}
346#[derive(Debug, Clone)]
347pub struct RCode {
348    code: [u8; REPLY_CODE_LENGTH],
349    bytes: BytesMut,
350}
351
352impl From<[u8; REPLY_CODE_LENGTH]> for RCode {
353    fn from(code: [u8; REPLY_CODE_LENGTH]) -> Self {
354        Self::new(code)
355    }
356}
357
358impl RCode {
359    pub fn new(code: [u8; REPLY_CODE_LENGTH]) -> Self {
360        Self {
361            code,
362            bytes: BytesMut::from_iter(code.iter().map(ToString::to_string).join("").as_bytes()),
363        }
364    }
365
366    fn parse(buffer: BytesMut) -> Result<Self, InvalidData> {
367        if buffer.len() < REPLY_CODE_LENGTH {
368            return Err(InvalidData {
369                msg: "Invalid length of code",
370                offending_bytes: buffer,
371            });
372        }
373        let mut code: [u8; 3] = [0_u8; REPLY_CODE_LENGTH];
374        for (pos, c_code) in code.iter_mut().enumerate() {
375            let Ok(number) = String::from_utf8_lossy(&[buffer[pos]]).parse::<u8>() else {
376                return Err(InvalidData {
377                    msg: "invalid u8 in code",
378                    offending_bytes: buffer,
379                });
380            };
381            *c_code = number;
382        }
383        Ok(Self {
384            code,
385            bytes: buffer,
386        })
387    }
388
389    /// The status code
390    #[must_use]
391    pub fn code(&self) -> [u8; REPLY_CODE_LENGTH] {
392        self.code
393    }
394
395    fn as_bytes(&self) -> &[u8] {
396        &self.bytes
397    }
398
399    fn len(&self) -> usize {
400        self.bytes.len()
401    }
402}
403
404#[cfg(test)]
405mod test {
406    use super::*;
407
408    #[test]
409    fn test_xcode_valid() {
410        let input = BytesMut::from_iter(b"1.20.3");
411        let code = XCode::parse(input).expect("Failed parsing input");
412
413        assert_eq!(code.code, [1, 20, 3]);
414
415        println!("{:?}", code.bytes);
416        assert_eq!(6, code.bytes.len());
417    }
418
419    #[test]
420    fn test_xcode_invalid() {
421        let input = BytesMut::from_iter(b"1.23");
422        let _code = XCode::parse(input).expect_err("Parsing did not error on invalid");
423    }
424
425    #[test]
426    fn test_rcode_valid() {
427        let input = BytesMut::from_iter(b"454");
428        let code = RCode::parse(input).expect("Failed parsing input");
429
430        assert_eq!(code.code, [4, 5, 4]);
431
432        println!("{:?}", code.bytes);
433        assert_eq!(code.bytes.as_ref(), b"454");
434    }
435
436    #[test]
437    fn test_rcode_invalid() {
438        let input = BytesMut::from_iter(b"4.54");
439        let _code = RCode::parse(input).expect_err("Parsing did not error on invalid");
440    }
441
442    #[test]
443    fn test_reply_parse() {
444        let input = BytesMut::from_iter(b"501 5.7.0 Client initiated Authentication Exchange\0");
445        let reply: Replycode = Parsable::parse(input).expect("Parsing failed");
446        assert_eq!(reply.rcode.as_bytes(), b"501");
447        assert_eq!(reply.xcode.expect("Parsing failed").as_bytes(), b"5.7.0");
448        assert_eq!(reply.message, "Client initiated Authentication Exchange");
449    }
450
451    #[test]
452    fn test_reply_parse_empty_xcode() {
453        let input =
454            BytesMut::from_iter(b"421 Service not available, closing transmission channel\0");
455        let reply: Replycode = Parsable::parse(input).expect("Parsing failed");
456        assert_eq!(reply.rcode.as_bytes(), b"421");
457        assert!(reply.xcode.is_none());
458        assert_eq!(
459            reply.message,
460            "Service not available, closing transmission channel"
461        );
462    }
463
464    #[test]
465    fn test_reply_write() {
466        let input = BytesMut::from_iter(b"501 5.7.0 Client initiated Authentication Exchange\0");
467        let reply: Replycode = Parsable::parse(input.clone()).expect("Parsing failed");
468        let mut output = BytesMut::new();
469        reply.write(&mut output);
470        assert_eq!(output.as_ref(), input.as_ref());
471    }
472
473    #[test]
474    fn test_reply_write_with_empty_xcode() {
475        let input =
476            BytesMut::from_iter(b"421 Service not available, closing transmission channel\0");
477        let reply: Replycode = Parsable::parse(input.clone()).expect("Parsing failed");
478        let mut output = BytesMut::new();
479        reply.write(&mut output);
480        assert_eq!(output.as_ref(), input.as_ref());
481    }
482}