Skip to main content

io_smtp/rfc5321/
greeting.rs

1//! SMTP greeting coroutine; reads the initial `220 <domain> …`
2//! banner sent right after the transport handshake.
3//!
4//! # Example
5//!
6//! ```rust,no_run
7//! use std::{
8//!     io::{Read, Write},
9//!     net::TcpStream,
10//! };
11//!
12//! use io_smtp::{
13//!     coroutine::{SmtpCoroutine, SmtpCoroutineState, SmtpYield},
14//!     rfc5321::greeting::SmtpGreetingGet,
15//! };
16//!
17//! // Ready stream needed (TCP-connected, TLS-negociated if implicit)
18//! let mut stream = TcpStream::connect("localhost:25").unwrap();
19//!
20//! let mut buf = [0u8; 4096];
21//!
22//! let mut coroutine = SmtpGreetingGet::new();
23//! let mut arg = None;
24//!
25//! let greeting = loop {
26//!     match coroutine.resume(arg.take()) {
27//!         SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => {
28//!             stream.write_all(&bytes).unwrap();
29//!         }
30//!         SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {
31//!             let n = stream.read(&mut buf).unwrap();
32//!             arg = Some(&buf[..n]);
33//!         }
34//!         SmtpCoroutineState::Complete(Ok(greeting)) => break greeting,
35//!         SmtpCoroutineState::Complete(Err(err)) => panic!("{err}"),
36//!     }
37//! };
38//!
39//! println!("{greeting:?}");
40//! ```
41
42use core::{fmt, mem};
43
44use alloc::{string::String, vec::Vec};
45
46use bounded_static::IntoBoundedStatic;
47use log::{debug, trace};
48use thiserror::Error;
49
50use crate::{
51    coroutine::*,
52    rfc5321::SmtpGreeting,
53    utils::{escape_byte_string, parsers::format_rich_errors},
54};
55
56/// Failure causes while reading the SMTP greeting.
57#[derive(Clone, Debug, Error)]
58pub enum SmtpGreetingGetError {
59    /// The stream reached EOF before a complete greeting arrived.
60    #[error("SMTP greeting failed: reached unexpected EOF on stream")]
61    Eof,
62    /// The banner could not be parsed as an SMTP greeting.
63    #[error("SMTP greeting failed: parse error: {0}")]
64    ParseResponse(String),
65}
66
67/// I/O-free SMTP greeting-read coroutine.
68pub struct SmtpGreetingGet {
69    state: State,
70    wants_read: bool,
71    buf: Vec<u8>,
72}
73
74impl SmtpGreetingGet {
75    /// Creates the coroutine.
76    pub fn new() -> Self {
77        Self {
78            state: State::Read,
79            wants_read: false,
80            buf: Vec::new(),
81        }
82    }
83}
84
85impl Default for SmtpGreetingGet {
86    fn default() -> Self {
87        Self::new()
88    }
89}
90
91impl SmtpCoroutine for SmtpGreetingGet {
92    type Yield = SmtpYield;
93    type Return = Result<SmtpGreeting<'static>, SmtpGreetingGetError>;
94
95    fn resume(&mut self, mut arg: Option<&[u8]>) -> SmtpCoroutineState<Self::Yield, Self::Return> {
96        loop {
97            if mem::take(&mut self.wants_read) {
98                return SmtpCoroutineState::Yielded(SmtpYield::WantsRead);
99            }
100
101            match &mut self.state {
102                State::Read => match arg.take() {
103                    Some(&[]) => {
104                        return SmtpCoroutineState::Complete(Err(SmtpGreetingGetError::Eof));
105                    }
106                    Some(data) => {
107                        trace!("read bytes: {}", escape_byte_string(data));
108                        self.buf.extend_from_slice(data);
109
110                        if !SmtpGreeting::is_complete(&self.buf) {
111                            self.wants_read = true;
112                            continue;
113                        }
114
115                        self.state = State::Parse;
116                        debug!("greeting complete, parsing");
117                    }
118                    None => {
119                        self.wants_read = true;
120                    }
121                },
122                State::Parse => {
123                    return match SmtpGreeting::parse(&self.buf) {
124                        Ok(greeting) => {
125                            let greeting = greeting.into_static();
126                            let _ = mem::take(&mut self.buf);
127                            debug!("greeting parsed");
128                            trace!("{greeting:?}");
129                            SmtpCoroutineState::Complete(Ok(greeting))
130                        }
131                        Err(errors) => {
132                            let reason = format_rich_errors(errors);
133                            SmtpCoroutineState::Complete(Err(SmtpGreetingGetError::ParseResponse(
134                                reason,
135                            )))
136                        }
137                    };
138                }
139            }
140        }
141    }
142}
143
144enum State {
145    Read,
146    Parse,
147}
148
149impl fmt::Display for State {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        match self {
152            Self::Read => f.write_str("read greeting"),
153            Self::Parse => f.write_str("parse greeting"),
154        }
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use crate::{coroutine::*, rfc5321::greeting::*};
161
162    #[test]
163    fn single_line_success_returns_ok() {
164        let mut greeting = SmtpGreetingGet::new();
165        expect_wants_read(&mut greeting);
166
167        let g = expect_complete_ok(&mut greeting, b"220 server.example.com ready\r\n");
168        assert_eq!(g.domain.0.as_ref(), "server.example.com");
169    }
170
171    #[test]
172    fn multi_line_success_returns_ok() {
173        let mut greeting = SmtpGreetingGet::new();
174        expect_wants_read(&mut greeting);
175
176        let reply = b"220-server.example.com hello\r\n220-extra info\r\n220 ready\r\n";
177        let g = expect_complete_ok(&mut greeting, reply);
178        assert_eq!(g.domain.0.as_ref(), "server.example.com");
179    }
180
181    #[test]
182    fn incomplete_greeting_re_yields_read() {
183        let mut greeting = SmtpGreetingGet::new();
184        expect_wants_read(&mut greeting);
185
186        // NOTE: partial line: missing CRLF
187        match greeting.resume(Some(b"220 server.example.com")) {
188            SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {}
189            state => panic!("expected WantsRead, got {state:?}"),
190        }
191    }
192
193    #[test]
194    fn parse_error_returns_parse_error() {
195        let mut greeting = SmtpGreetingGet::new();
196        expect_wants_read(&mut greeting);
197
198        // NOTE: 250 is not a valid greeting code
199        let err = expect_complete_err(&mut greeting, b"250 wrong code\r\n");
200        assert!(matches!(err, SmtpGreetingGetError::ParseResponse(_)));
201    }
202
203    #[test]
204    fn eof_returns_eof_error() {
205        let mut greeting = SmtpGreetingGet::new();
206        expect_wants_read(&mut greeting);
207
208        let err = expect_complete_err(&mut greeting, b"");
209        assert!(matches!(err, SmtpGreetingGetError::Eof));
210    }
211
212    fn expect_wants_read(cor: &mut SmtpGreetingGet) {
213        match cor.resume(None) {
214            SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {}
215            state => panic!("expected WantsRead, got {state:?}"),
216        }
217    }
218
219    fn expect_complete_ok(cor: &mut SmtpGreetingGet, reply: &[u8]) -> SmtpGreeting<'static> {
220        match cor.resume(Some(reply)) {
221            SmtpCoroutineState::Complete(Ok(value)) => value,
222            state => panic!("expected Complete(Ok), got {state:?}"),
223        }
224    }
225
226    fn expect_complete_err(cor: &mut SmtpGreetingGet, reply: &[u8]) -> SmtpGreetingGetError {
227        match cor.resume(Some(reply)) {
228            SmtpCoroutineState::Complete(Err(err)) => err,
229            state => panic!("expected Complete(Err), got {state:?}"),
230        }
231    }
232}