Commit
+74 -69 +/-4 browse
1 | diff --git a/maitred/src/error.rs b/maitred/src/error.rs |
2 | index 4ef87fa..1694437 100644 |
3 | --- a/maitred/src/error.rs |
4 | +++ b/maitred/src/error.rs |
5 | @@ -8,16 +8,22 @@ use url::ParseError; |
6 | /// to shutdown and stop processing connections. |
7 | #[derive(Debug, thiserror::Error)] |
8 | pub enum Error { |
9 | + /// An unspecified internal error |
10 | #[error("Unspecified internal error: {0}")] |
11 | Internal(String), |
12 | + /// An IO related error such as not being able to bind to a TCP socket |
13 | #[error("Io: {0}")] |
14 | Io(#[from] std::io::Error), |
15 | + /// An error generated from the underlying SMTP protocol |
16 | #[error("Smtp failure: {0}")] |
17 | Smtp(#[from] SmtpError), |
18 | + /// Inability to parse a URL |
19 | #[error("Failed to parse Url: {0}")] |
20 | UrlParsing(#[from] ParseError), |
21 | + /// UTF8 related Error |
22 | #[error("Failed to read UTF8: {0}")] |
23 | Utf8(#[from] FromUtf8Error), |
24 | + /// Session timeout |
25 | #[error("Client took too long to respond: {0}s")] |
26 | Timeout(u64), |
27 | } |
28 | diff --git a/maitred/src/lib.rs b/maitred/src/lib.rs |
29 | index e9a537b..c13f738 100644 |
30 | --- a/maitred/src/lib.rs |
31 | +++ b/maitred/src/lib.rs |
32 | @@ -34,10 +34,11 @@ use transport::Response; |
33 | |
34 | pub use error::Error; |
35 | pub use expand::{Error as ExpansionError, Expansion, Func as ExpansionFunc}; |
36 | + pub use pipeline::{Pipeline, Transaction}; |
37 | pub use server::Server; |
38 | pub use session::{ |
39 | - Options as SessionOptions, DEFAULT_CAPABILITIES, DEFAULT_GREETING, DEFAULT_HELP_BANNER, |
40 | - DEFAULT_MAXIMUM_MESSAGE_SIZE, |
41 | + Options as SessionOptions, Session, DEFAULT_CAPABILITIES, DEFAULT_GREETING, |
42 | + DEFAULT_HELP_BANNER, DEFAULT_MAXIMUM_MESSAGE_SIZE, |
43 | }; |
44 | pub use verify::{Error as VerifyError, Func as VerifyFunc, Verify}; |
45 | |
46 | @@ -47,64 +48,8 @@ pub use smtp_proto; |
47 | /// Chunk is a logical set of SMTP resposnes that might be generated from one |
48 | /// command or "pipelined" as the result of several commands sent by the client |
49 | /// that do not require immediate responses. |
50 | - #[derive(Clone, Debug)] |
51 | - pub(crate) struct Chunk(pub Vec<Response<String>>); |
52 | - |
53 | - /// Generate a single smtp_response |
54 | - macro_rules! smtp_response { |
55 | - ($code:expr, $e1:expr, $e2:expr, $e3:expr, $name:expr) => { |
56 | - Response::General(SmtpResponse::new($code, $e1, $e2, $e3, $name.to_string())) |
57 | - }; |
58 | - } |
59 | - pub(crate) use smtp_response; |
60 | - |
61 | - /// Generate an SMTP response |
62 | - macro_rules! smtp_chunk { |
63 | - ($code:expr, $e1:expr, $e2:expr, $e3:expr, $name:expr) => { |
64 | - Chunk(vec![Response::General(SmtpResponse::new( |
65 | - $code, |
66 | - $e1, |
67 | - $e2, |
68 | - $e3, |
69 | - $name.to_string(), |
70 | - ))]) |
71 | - }; |
72 | - } |
73 | - pub(crate) use smtp_chunk; |
74 | - |
75 | - /// Generate an SMTP response error |
76 | - macro_rules! smtp_chunk_ok { |
77 | - ($code:expr, $e1:expr, $e2:expr, $e3:expr, $name:expr) => { |
78 | - Ok::<Chunk, Chunk>(Chunk(vec![Response::General(SmtpResponse::new( |
79 | - $code, |
80 | - $e1, |
81 | - $e2, |
82 | - $e3, |
83 | - $name.to_string(), |
84 | - ))])) |
85 | - }; |
86 | - } |
87 | - pub(crate) use smtp_chunk_ok; |
88 | - |
89 | - /// Generate an SMTP response error |
90 | - macro_rules! smtp_chunk_err { |
91 | - ($code:expr, $e1:expr, $e2:expr, $e3:expr, $name:expr) => { |
92 | - Err::<Chunk, Chunk>(Chunk(vec![Response::General(SmtpResponse::new( |
93 | - $code, |
94 | - $e1, |
95 | - $e2, |
96 | - $e3, |
97 | - $name.to_string(), |
98 | - ))])) |
99 | - }; |
100 | - } |
101 | - pub(crate) use smtp_chunk_err; |
102 | - |
103 | - impl Chunk { |
104 | - pub fn new() -> Self { |
105 | - Chunk(vec![]) |
106 | - } |
107 | - } |
108 | + #[derive(Clone, Debug, Default)] |
109 | + pub struct Chunk(pub Vec<Response<String>>); |
110 | |
111 | impl PartialEq for Chunk { |
112 | fn eq(&self, other: &Self) -> bool { |
113 | @@ -153,3 +98,53 @@ impl From<crate::expand::Error> for Chunk { |
114 | } |
115 | } |
116 | } |
117 | + |
118 | + /// Generate a single smtp_response |
119 | + macro_rules! smtp_response { |
120 | + ($code:expr, $e1:expr, $e2:expr, $e3:expr, $name:expr) => { |
121 | + Response::General(SmtpResponse::new($code, $e1, $e2, $e3, $name.to_string())) |
122 | + }; |
123 | + } |
124 | + pub(crate) use smtp_response; |
125 | + |
126 | + /// Generate a Chunk that contains a single SMTP response. |
127 | + #[macro_export] |
128 | + macro_rules! smtp_chunk { |
129 | + ($code:expr, $e1:expr, $e2:expr, $e3:expr, $name:expr) => { |
130 | + Chunk(vec![Response::General(SmtpResponse::new( |
131 | + $code, |
132 | + $e1, |
133 | + $e2, |
134 | + $e3, |
135 | + $name.to_string(), |
136 | + ))]) |
137 | + }; |
138 | + } |
139 | + |
140 | + /// Generate a Chunk that contains a single successful response. |
141 | + #[macro_export] |
142 | + macro_rules! smtp_chunk_ok { |
143 | + ($code:expr, $e1:expr, $e2:expr, $e3:expr, $name:expr) => { |
144 | + Ok::<Chunk, Chunk>(Chunk(vec![Response::General(SmtpResponse::new( |
145 | + $code, |
146 | + $e1, |
147 | + $e2, |
148 | + $e3, |
149 | + $name.to_string(), |
150 | + ))])) |
151 | + }; |
152 | + } |
153 | + |
154 | + /// Generate a Chunk that that contains a single error. |
155 | + #[macro_export] |
156 | + macro_rules! smtp_chunk_err { |
157 | + ($code:expr, $e1:expr, $e2:expr, $e3:expr, $name:expr) => { |
158 | + Err::<Chunk, Chunk>(Chunk(vec![Response::General(SmtpResponse::new( |
159 | + $code, |
160 | + $e1, |
161 | + $e2, |
162 | + $e3, |
163 | + $name.to_string(), |
164 | + ))])) |
165 | + }; |
166 | + } |
167 | diff --git a/maitred/src/pipeline.rs b/maitred/src/pipeline.rs |
168 | index 7ad02b3..353ed27 100644 |
169 | --- a/maitred/src/pipeline.rs |
170 | +++ b/maitred/src/pipeline.rs |
171 | @@ -108,8 +108,8 @@ impl Pipeline { |
172 | self.history.clear(); |
173 | res.clone().unwrap_or_else(|e| e) |
174 | } |
175 | - Request::Mail { from: _ } => Chunk::new(), |
176 | - Request::Rcpt { to: _ } => Chunk::new(), |
177 | + Request::Mail { from: _ } => Chunk::default(), |
178 | + Request::Rcpt { to: _ } => Chunk::default(), |
179 | Request::Bdat { |
180 | chunk_size: _, |
181 | is_last: _, |
182 | @@ -119,7 +119,7 @@ impl Pipeline { |
183 | self.history.clear(); |
184 | chunk |
185 | } else { |
186 | - Chunk::new() |
187 | + Chunk::default() |
188 | } |
189 | } |
190 | Request::Auth { |
191 | @@ -140,7 +140,7 @@ impl Pipeline { |
192 | self.history.clear(); |
193 | chunk |
194 | } else { |
195 | - Chunk::new() |
196 | + Chunk::default() |
197 | } |
198 | } |
199 | Request::Rset => { |
200 | diff --git a/maitred/src/session.rs b/maitred/src/session.rs |
201 | index 47a143e..03d6fce 100644 |
202 | --- a/maitred/src/session.rs |
203 | +++ b/maitred/src/session.rs |
204 | @@ -126,9 +126,9 @@ impl Options { |
205 | } |
206 | } |
207 | |
208 | - /// Stateful connection that coresponds to a single SMTP session |
209 | + /// Stateful connection that coresponds to a single SMTP session. |
210 | #[derive(Default)] |
211 | - pub(crate) struct Session { |
212 | + pub struct Session { |
213 | /// message body |
214 | pub body: Option<Vec<u8>>, |
215 | /// mailto address |
216 | @@ -145,11 +145,14 @@ pub(crate) struct Session { |
217 | } |
218 | |
219 | impl Session { |
220 | + /// Configure a session with various options that effect it's behavior. |
221 | pub fn with_options(mut self, opts: Rc<Options>) -> Self { |
222 | self.opts = opts; |
223 | self |
224 | } |
225 | |
226 | + /// Reset the connection to it's default state but after a HELO/ELHO has |
227 | + /// been issued successfully. |
228 | pub fn reset(&mut self) { |
229 | self.body = None; |
230 | self.mail_from = None; |
231 | @@ -170,14 +173,15 @@ impl Session { |
232 | ) |
233 | } |
234 | |
235 | - /// If the session is in extended mode i.e. EHLO was sent |
236 | + /// If the session is in extended mode i.e. EHLO was sent in the beginning |
237 | + /// of the connection. |
238 | pub fn is_extended(&self) -> bool { |
239 | self.initialized |
240 | .as_ref() |
241 | .is_some_and(|mode| matches!(mode, Mode::Extended)) |
242 | } |
243 | |
244 | - /// check if the capability is supported by the session |
245 | + /// Check if the capability is supported by the session |
246 | pub fn has_capability(&self, capability: u32) -> bool { |
247 | self.initialized |
248 | .as_ref() |
249 | @@ -185,7 +189,7 @@ impl Session { |
250 | && self.opts.capabilities & capability != 0 |
251 | } |
252 | |
253 | - /// ensure that the session has been initialized otherwise return an error |
254 | + /// Ensure that the session has been initialized otherwise return an error |
255 | fn check_initialized(&self) -> StdResult<(), Chunk> { |
256 | if self.initialized.is_none() { |
257 | return Err(smtp_chunk!(500, 0, 0, 0, "It's polite to say EHLO first")); |