Commit
+163 -119 +/-4 browse
1 | diff --git a/maitred/src/lib.rs b/maitred/src/lib.rs |
2 | index e523b2c..7ea9531 100644 |
3 | --- a/maitred/src/lib.rs |
4 | +++ b/maitred/src/lib.rs |
5 | @@ -7,46 +7,79 @@ mod session; |
6 | mod transport; |
7 | mod verify; |
8 | |
9 | - use smtp_proto::{Request, Response as SmtpResponse}; |
10 | + use smtp_proto::Request; |
11 | |
12 | /// Low Level SMTP protocol is exported for convenience |
13 | pub use smtp_proto; |
14 | |
15 | pub use error::Error; |
16 | pub use server::Server; |
17 | + use transport::Response; |
18 | |
19 | - /// Generate an SMTP response |
20 | - #[macro_export(local_inner_macros)] |
21 | + /// Chunk is a logical set of SMTP resposnes that might be generated from one |
22 | + /// command or "pipelined" as the result of several commands sent by the client |
23 | + /// that do not require immediate responses. |
24 | + #[derive(Clone, Debug)] |
25 | + pub(crate) struct Chunk(pub Vec<Response<String>>); |
26 | + |
27 | + impl Chunk { |
28 | + pub fn new() -> Self { |
29 | + Chunk(vec![]) |
30 | + } |
31 | + } |
32 | + |
33 | + impl PartialEq for Chunk { |
34 | + fn eq(&self, other: &Self) -> bool { |
35 | + self.0.len() == other.0.len() && self.0.iter().zip(other.0.iter()).all(|(a, b)| a == b) |
36 | + } |
37 | + } |
38 | + |
39 | + /// Generate a single smtp_response |
40 | macro_rules! smtp_response { |
41 | ($code:expr, $e1:expr, $e2:expr, $e3:expr, $name:expr) => { |
42 | Response::General(SmtpResponse::new($code, $e1, $e2, $e3, $name.to_string())) |
43 | }; |
44 | } |
45 | + pub(crate) use smtp_response; |
46 | |
47 | - /// Generate a successful SMTP response |
48 | - #[macro_export(local_inner_macros)] |
49 | - macro_rules! smtp_ok { |
50 | + /// Generate an SMTP response |
51 | + macro_rules! smtp_chunk { |
52 | + ($code:expr, $e1:expr, $e2:expr, $e3:expr, $name:expr) => { |
53 | + Chunk(vec![Response::General(SmtpResponse::new( |
54 | + $code, |
55 | + $e1, |
56 | + $e2, |
57 | + $e3, |
58 | + $name.to_string(), |
59 | + ))]) |
60 | + }; |
61 | + } |
62 | + pub(crate) use smtp_chunk; |
63 | + |
64 | + /// Generate an SMTP response error |
65 | + macro_rules! smtp_chunk_ok { |
66 | ($code:expr, $e1:expr, $e2:expr, $e3:expr, $name:expr) => { |
67 | - Ok::<Response<String>, Response<String>>(Response::General(SmtpResponse::new( |
68 | + Ok::<Chunk, Chunk>(Chunk(vec![Response::General(SmtpResponse::new( |
69 | $code, |
70 | $e1, |
71 | $e2, |
72 | $e3, |
73 | $name.to_string(), |
74 | - ))) |
75 | + ))])) |
76 | }; |
77 | } |
78 | + pub(crate) use smtp_chunk_ok; |
79 | |
80 | /// Generate an SMTP response error |
81 | - #[macro_export(local_inner_macros)] |
82 | - macro_rules! smtp_err { |
83 | + macro_rules! smtp_chunk_err { |
84 | ($code:expr, $e1:expr, $e2:expr, $e3:expr, $name:expr) => { |
85 | - Err::<Response<String>, Response<String>>(Response::General(SmtpResponse::new( |
86 | + Err::<Chunk, Chunk>(Chunk(vec![Response::General(SmtpResponse::new( |
87 | $code, |
88 | $e1, |
89 | $e2, |
90 | $e3, |
91 | $name.to_string(), |
92 | - ))) |
93 | + ))])) |
94 | }; |
95 | } |
96 | + pub(crate) use smtp_chunk_err; |
97 | diff --git a/maitred/src/pipeline.rs b/maitred/src/pipeline.rs |
98 | index 750f90d..cc99703 100644 |
99 | --- a/maitred/src/pipeline.rs |
100 | +++ b/maitred/src/pipeline.rs |
101 | @@ -1,10 +1,22 @@ |
102 | use crate::session::Result as SessionResult; |
103 | - use crate::transport::Response; |
104 | - use crate::Request; |
105 | + use crate::{Chunk, Request}; |
106 | |
107 | - pub type Result = Vec<Response<String>>; |
108 | pub type Transaction = (Request<String>, SessionResult); |
109 | |
110 | + fn flatten(history: &[Transaction]) -> Chunk { |
111 | + Chunk( |
112 | + history |
113 | + .iter() |
114 | + .map(|tx| tx.1.clone().unwrap_or_else(|e| e)) |
115 | + .fold(Vec::new(), |mut accm, chunk| { |
116 | + accm.extend(chunk.0.clone()); |
117 | + accm |
118 | + }) |
119 | + .into_iter() |
120 | + .collect(), |
121 | + ) |
122 | + } |
123 | + |
124 | /// Pipeline chunks session request/responses into logical groups returning |
125 | /// a response only once a session is considered "completed". |
126 | #[derive(Default)] |
127 | @@ -31,7 +43,7 @@ impl Pipeline { |
128 | .is_some_and(|is_tx| is_tx) |
129 | } |
130 | |
131 | - fn chunk(&mut self) -> Result { |
132 | + fn chunk(&mut self) -> Chunk { |
133 | let mail_from_ok = self.history.iter().fold(false, |ok, tx| { |
134 | if matches!(&tx.0, Request::Mail { from: _ }) { |
135 | return tx.1.is_ok(); |
136 | @@ -49,27 +61,15 @@ impl Pipeline { |
137 | .last() |
138 | .expect("to results called without history"); |
139 | if last_command.1.is_ok() && mail_from_ok && rcpt_to_ok_count > 0 { |
140 | - self.history |
141 | - .iter() |
142 | - .map(|tx| tx.1.clone().unwrap_or_else(|e| e)) |
143 | - .collect() |
144 | + flatten(&self.history) |
145 | } else if !mail_from_ok { |
146 | self.history.pop(); |
147 | - self.history |
148 | - .iter() |
149 | - .map(|tx| tx.1.clone().unwrap_or_else(|e| e)) |
150 | - .collect() |
151 | + flatten(&self.history) |
152 | } else if !rcpt_to_ok_count <= 0 { |
153 | self.history.pop(); |
154 | - self.history |
155 | - .iter() |
156 | - .map(|tx| tx.1.clone().unwrap_or_else(|e| e)) |
157 | - .collect() |
158 | + flatten(&self.history) |
159 | } else { |
160 | - self.history |
161 | - .iter() |
162 | - .map(|tx| tx.1.clone().unwrap_or_else(|e| e)) |
163 | - .collect() |
164 | + flatten(&self.history) |
165 | } |
166 | } |
167 | |
168 | @@ -89,7 +89,7 @@ impl Pipeline { |
169 | /// results if the command requires a server response. Returns true if |
170 | /// the process was successful or false if it contained fatal errors. All |
171 | /// results must be passed to the client. |
172 | - pub fn process(&mut self, req: &Request<String>, res: &SessionResult) -> Result { |
173 | + pub fn process(&mut self, req: &Request<String>, res: &SessionResult) -> Chunk { |
174 | let is_data_tx = self.within_tx(); |
175 | if is_data_tx { |
176 | // ignore the first data request |
177 | @@ -101,18 +101,18 @@ impl Pipeline { |
178 | match req { |
179 | Request::Ehlo { host: _ } => { |
180 | self.history.clear(); |
181 | - vec![res.clone().unwrap_or_else(|e| e)] |
182 | + res.clone().unwrap_or_else(|e| e) |
183 | } |
184 | Request::Lhlo { host: _ } => { |
185 | self.history.clear(); |
186 | - vec![res.clone().unwrap_or_else(|e| e)] |
187 | + res.clone().unwrap_or_else(|e| e) |
188 | } |
189 | Request::Helo { host: _ } => { |
190 | self.history.clear(); |
191 | - vec![res.clone().unwrap_or_else(|e| e)] |
192 | + res.clone().unwrap_or_else(|e| e) |
193 | } |
194 | - Request::Mail { from: _ } => vec![], |
195 | - Request::Rcpt { to: _ } => vec![], |
196 | + Request::Mail { from: _ } => Chunk::new(), |
197 | + Request::Rcpt { to: _ } => Chunk::new(), |
198 | Request::Bdat { |
199 | chunk_size: _, |
200 | is_last: _, |
201 | @@ -122,17 +122,17 @@ impl Pipeline { |
202 | self.history.clear(); |
203 | chunk |
204 | } else { |
205 | - vec![] |
206 | + Chunk::new() |
207 | } |
208 | } |
209 | Request::Auth { |
210 | mechanism: _, |
211 | initial_response: _, |
212 | } => todo!(), |
213 | - Request::Noop { value: _ } => vec![res.clone().unwrap_or_else(|e| e)], |
214 | + Request::Noop { value: _ } => res.clone().unwrap_or_else(|e| e), |
215 | Request::Vrfy { value: _ } => todo!(), |
216 | Request::Expn { value: _ } => todo!(), |
217 | - Request::Help { value: _ } => vec![res.clone().unwrap_or_else(|e| e)], |
218 | + Request::Help { value: _ } => res.clone().unwrap_or_else(|e| e), |
219 | Request::Etrn { name: _ } => todo!(), |
220 | Request::Atrn { domains: _ } => todo!(), |
221 | Request::Burl { uri: _, is_last: _ } => todo!(), |
222 | @@ -143,14 +143,14 @@ impl Pipeline { |
223 | self.history.clear(); |
224 | chunk |
225 | } else { |
226 | - vec![] |
227 | + Chunk::new() |
228 | } |
229 | } |
230 | Request::Rset => { |
231 | self.history.clear(); |
232 | - vec![res.clone().unwrap_or_else(|e| e)] |
233 | + res.clone().unwrap_or_else(|e| e) |
234 | } |
235 | - Request::Quit => vec![res.clone().unwrap_or_else(|e| e)], |
236 | + Request::Quit => res.clone().unwrap_or_else(|e| e), |
237 | } |
238 | } |
239 | } |
240 | @@ -159,7 +159,9 @@ impl Pipeline { |
241 | mod test { |
242 | |
243 | use super::*; |
244 | - use crate::{smtp_ok, Request, SmtpResponse}; |
245 | + use crate::{ |
246 | + smtp_chunk_ok, smtp_proto::Response as SmtpResponse, transport::Response, Request, |
247 | + }; |
248 | |
249 | #[test] |
250 | pub fn test_pipeline_basic() { |
251 | @@ -170,8 +172,9 @@ mod test { |
252 | &Request::Helo { |
253 | host: "example.org".to_string(), |
254 | }, |
255 | - &smtp_ok!(200, 0, 0, 0, "OK") |
256 | + &smtp_chunk_ok!(200, 0, 0, 0, "OK") |
257 | ) |
258 | + .0 |
259 | .len() |
260 | == 1 |
261 | ); |
262 | @@ -184,8 +187,9 @@ mod test { |
263 | ..Default::default() |
264 | }, |
265 | }, |
266 | - &smtp_ok!(200, 0, 0, 0, "OK: baz@qux.com") |
267 | + &smtp_chunk_ok!(200, 0, 0, 0, "OK: baz@qux.com") |
268 | ) |
269 | + .0 |
270 | .is_empty()); |
271 | assert!(pipeline |
272 | .process( |
273 | @@ -195,16 +199,18 @@ mod test { |
274 | ..Default::default() |
275 | } |
276 | }, |
277 | - &smtp_ok!(200, 0, 0, 0, "OK: fuu@bar.com") |
278 | + &smtp_chunk_ok!(200, 0, 0, 0, "OK: fuu@bar.com") |
279 | ) |
280 | + .0 |
281 | .is_empty()); |
282 | |
283 | // initialize a data request |
284 | assert!(pipeline |
285 | - .process(&Request::Data {}, &smtp_ok!(200, 0, 0, 0, "OK")) |
286 | + .process(&Request::Data {}, &smtp_chunk_ok!(200, 0, 0, 0, "OK")) |
287 | + .0 |
288 | .is_empty()); |
289 | // simulate the end of a request |
290 | - let result = pipeline.process(&Request::Data {}, &smtp_ok!(200, 0, 0, 0, "OK")); |
291 | - assert!(result.len() == 3); |
292 | + let result = pipeline.process(&Request::Data {}, &smtp_chunk_ok!(200, 0, 0, 0, "OK")); |
293 | + assert!(result.0.len() == 3); |
294 | } |
295 | } |
296 | diff --git a/maitred/src/server.rs b/maitred/src/server.rs |
297 | index 4d1fb2e..507c947 100644 |
298 | --- a/maitred/src/server.rs |
299 | +++ b/maitred/src/server.rs |
300 | @@ -4,13 +4,14 @@ use bytes::Bytes; |
301 | use futures::SinkExt; |
302 | use smtp_proto::Request; |
303 | use tokio::{net::TcpListener, time::timeout}; |
304 | - use tokio_stream::{self as stream, StreamExt}; |
305 | + use tokio_stream::StreamExt; |
306 | use tokio_util::codec::Framed; |
307 | |
308 | use crate::error::Error; |
309 | use crate::pipeline::Pipeline; |
310 | use crate::session::Session; |
311 | - use crate::transport::{Response, Transport}; |
312 | + use crate::transport::Transport; |
313 | + use crate::Chunk; |
314 | |
315 | const DEFAULT_LISTEN_ADDR: &str = "127.0.0.1:2525"; |
316 | const DEFAULT_GREETING: &str = "Maitred ESMTP Server"; |
317 | @@ -48,7 +49,7 @@ struct ConditionalPipeline<'a> { |
318 | } |
319 | |
320 | impl ConditionalPipeline<'_> { |
321 | - pub fn apply(&mut self, req: &Request<String>, data: Option<&Bytes>) -> Vec<Response<String>> { |
322 | + pub fn apply(&mut self, req: &Request<String>, data: Option<&Bytes>) -> Chunk { |
323 | let response = self.session.process(req, data); |
324 | if self.session.has_capability(smtp_proto::EXT_PIPELINING) && self.session.is_extended() { |
325 | self.pipeline.process(req, &response) |
326 | @@ -56,11 +57,11 @@ impl ConditionalPipeline<'_> { |
327 | match response { |
328 | Ok(response) => { |
329 | tracing::debug!("Client response: {:?}", response); |
330 | - vec![response] |
331 | + response |
332 | } |
333 | Err(response) => { |
334 | tracing::warn!("Client error: {:?}", response); |
335 | - vec![response] |
336 | + response |
337 | } |
338 | } |
339 | } |
340 | @@ -157,7 +158,7 @@ impl Server { |
341 | finished = true; |
342 | } |
343 | let responses = pipelined.apply(&command.0, command.1.as_ref()); |
344 | - for response in responses { |
345 | + for response in responses.0.into_iter() { |
346 | framed.send(response).await?; |
347 | } |
348 | if finished { |
349 | diff --git a/maitred/src/session.rs b/maitred/src/session.rs |
350 | index dcba00d..d1598b7 100644 |
351 | --- a/maitred/src/session.rs |
352 | +++ b/maitred/src/session.rs |
353 | @@ -10,11 +10,12 @@ use url::Host; |
354 | use crate::expansion::Expansion; |
355 | use crate::transport::Response; |
356 | use crate::verify::Verify; |
357 | - use crate::{smtp_err, smtp_ok, smtp_response}; |
358 | + use crate::{smtp_chunk, smtp_chunk_err, smtp_chunk_ok}; |
359 | + use crate::{smtp_response, Chunk}; |
360 | |
361 | /// Result generated as part of an SMTP session, an Err indicates a session |
362 | /// level error that will be returned to the client. |
363 | - pub type Result = StdResult<Response<String>, Response<String>>; |
364 | + pub type Result = StdResult<Chunk, Chunk>; |
365 | |
366 | enum Mode { |
367 | Legacy, |
368 | @@ -119,23 +120,17 @@ impl Session { |
369 | } |
370 | |
371 | /// ensure that the session has been initialized otherwise return an error |
372 | - fn check_initialized(&self) -> StdResult<(), Response<String>> { |
373 | + fn check_initialized(&self) -> StdResult<(), Chunk> { |
374 | if self.initialized.is_none() { |
375 | - return Err(smtp_response!( |
376 | - 500, |
377 | - 0, |
378 | - 0, |
379 | - 0, |
380 | - "It's polite to say EHLO first" |
381 | - )); |
382 | + return Err(smtp_chunk!(500, 0, 0, 0, "It's polite to say EHLO first")); |
383 | } |
384 | Ok(()) |
385 | } |
386 | |
387 | /// checks if 8BITMIME is supported |
388 | - fn check_body(&self, body: &[u8]) -> StdResult<(), Response<String>> { |
389 | + fn check_body(&self, body: &[u8]) -> StdResult<(), Chunk> { |
390 | if !self.has_capability(smtp_proto::EXT_8BIT_MIME) && !body.is_ascii() { |
391 | - return Err(smtp_response!( |
392 | + return Err(smtp_chunk!( |
393 | 500, |
394 | 0, |
395 | 0, |
396 | @@ -157,35 +152,32 @@ impl Session { |
397 | pub fn process(&mut self, req: &Request<String>, data: Option<&Bytes>) -> Result { |
398 | match req { |
399 | Request::Ehlo { host } => { |
400 | - self.hostname = Some(Host::parse(host).map_err(|e| { |
401 | - Response::General(SmtpResponse::new(500, 0, 0, 0, e.to_string())) |
402 | - })?); |
403 | + self.hostname = |
404 | + Some(Host::parse(host).map_err(|e| smtp_chunk!(500, 0, 0, 0, e.to_string()))?); |
405 | self.reset(); |
406 | self.initialized = Some(Mode::Extended); |
407 | let mut resp = EhloResponse::new(format!("Hello {}", host)); |
408 | resp.capabilities = self.capabilities; |
409 | resp.size = self.maximum_size as usize; |
410 | - Ok(Response::Ehlo(resp)) |
411 | + Ok(Chunk(vec![Response::Ehlo(resp)])) |
412 | } |
413 | Request::Lhlo { host } => { |
414 | - self.hostname = |
415 | - Some(Host::parse(host).map_err(|e| smtp_response!(500, 0, 0, 0, e))?); |
416 | + self.hostname = Some(Host::parse(host).map_err(|e| smtp_chunk!(500, 0, 0, 0, e))?); |
417 | self.reset(); |
418 | self.initialized = Some(Mode::Legacy); |
419 | - smtp_ok!(250, 0, 0, 0, format!("Hello {}", host)) |
420 | + smtp_chunk_ok!(250, 0, 0, 0, format!("Hello {}", host)) |
421 | } |
422 | Request::Helo { host } => { |
423 | - self.hostname = Some( |
424 | - Host::parse(host).map_err(|e| smtp_response!(500, 0, 0, 0, e.to_string()))?, |
425 | - ); |
426 | + self.hostname = |
427 | + Some(Host::parse(host).map_err(|e| smtp_chunk!(500, 0, 0, 0, e.to_string()))?); |
428 | self.reset(); |
429 | self.initialized = Some(Mode::Legacy); |
430 | - smtp_ok!(250, 0, 0, 0, format!("Hello {}", host)) |
431 | + smtp_chunk_ok!(250, 0, 0, 0, format!("Hello {}", host)) |
432 | } |
433 | Request::Mail { from } => { |
434 | self.check_initialized()?; |
435 | let mail_from = EmailAddress::from_str(from.address.as_str()).map_err(|e| { |
436 | - smtp_response!( |
437 | + smtp_chunk!( |
438 | 500, |
439 | 0, |
440 | 0, |
441 | @@ -194,19 +186,19 @@ impl Session { |
442 | ) |
443 | })?; |
444 | self.mail_from = Some(mail_from.clone()); |
445 | - smtp_ok!(250, 0, 0, 0, "OK") |
446 | + smtp_chunk_ok!(250, 0, 0, 0, "OK") |
447 | } |
448 | Request::Rcpt { to } => { |
449 | self.check_initialized()?; |
450 | let rcpt_to = EmailAddress::from_str(to.address.as_str()).map_err(|e| { |
451 | - smtp_response!(500, 0, 0, 0, format!("cannot parse: {} {}", to.address, e)) |
452 | + smtp_chunk!(500, 0, 0, 0, format!("cannot parse: {} {}", to.address, e)) |
453 | })?; |
454 | if let Some(ref mut rcpts) = self.rcpt_to { |
455 | rcpts.push(rcpt_to.clone()); |
456 | } else { |
457 | self.rcpt_to = Some(vec![rcpt_to.clone()]); |
458 | } |
459 | - smtp_ok!(250, 0, 0, 0, "OK") |
460 | + smtp_chunk_ok!(250, 0, 0, 0, "OK") |
461 | } |
462 | Request::Bdat { |
463 | chunk_size: _, |
464 | @@ -226,8 +218,8 @@ impl Session { |
465 | self.check_body(&message_payload)?; |
466 | let parser = MessageParser::new(); |
467 | let response = match parser.parse(&message_payload) { |
468 | - Some(_) => smtp_ok!(250, 0, 0, 0, "OK"), |
469 | - None => smtp_err!( |
470 | + Some(_) => smtp_chunk_ok!(250, 0, 0, 0, "OK"), |
471 | + None => smtp_chunk_err!( |
472 | 500, |
473 | 0, |
474 | 0, |
475 | @@ -243,7 +235,7 @@ impl Session { |
476 | } else { |
477 | tracing::info!("Initializing data transfer mode"); |
478 | self.data_transfer = Some(DataTransfer::Bdat); |
479 | - smtp_ok!(354, 0, 0, 0, "Starting BDAT data transfer".to_string()) |
480 | + smtp_chunk_ok!(354, 0, 0, 0, "Starting BDAT data transfer".to_string()) |
481 | } |
482 | } |
483 | Request::Auth { |
484 | @@ -252,42 +244,42 @@ impl Session { |
485 | } => todo!(), |
486 | Request::Noop { value: _ } => { |
487 | self.check_initialized()?; |
488 | - smtp_ok!(250, 0, 0, 0, "OK".to_string()) |
489 | + smtp_chunk_ok!(250, 0, 0, 0, "OK".to_string()) |
490 | } |
491 | Request::Vrfy { value } => { |
492 | if let Some(verifier) = &self.verification { |
493 | let address = EmailAddress::from_str(value.as_str()).map_err(|e| { |
494 | - smtp_response!(500, 0, 0, 0, format!("cannot parse: {} {}", value, e)) |
495 | + smtp_chunk!(500, 0, 0, 0, format!("cannot parse: {} {}", value, e)) |
496 | })?; |
497 | match verifier.verify(&address) { |
498 | Ok(_) => { |
499 | - smtp_ok!(200, 0, 0, 0, "Ok".to_string()) |
500 | + smtp_chunk_ok!(200, 0, 0, 0, "Ok".to_string()) |
501 | } |
502 | Err(e) => match e { |
503 | crate::verify::Error::Server(e) => { |
504 | - smtp_err!(500, 0, 0, 0, e.to_string()) |
505 | + smtp_chunk_err!(500, 0, 0, 0, e.to_string()) |
506 | } |
507 | crate::verify::Error::NotFound(e) => { |
508 | - smtp_err!(500, 0, 0, 0, e.to_string()) |
509 | + smtp_chunk_err!(500, 0, 0, 0, e.to_string()) |
510 | } |
511 | crate::verify::Error::Ambiguous(alternatives) => { |
512 | - smtp_err!(500, 0, 0, 0, alternatives.to_string()) |
513 | + smtp_chunk_err!(500, 0, 0, 0, alternatives.to_string()) |
514 | } |
515 | }, |
516 | } |
517 | } else { |
518 | - smtp_err!(500, 0, 0, 0, "No such address") |
519 | + smtp_chunk_err!(500, 0, 0, 0, "No such address") |
520 | } |
521 | } |
522 | Request::Expn { value } => { |
523 | if let Some(expn) = &self.list_expansion { |
524 | match expn.expand(value) { |
525 | Ok(addresses) => { |
526 | - smtp_ok!(250, 0, 0, 0, addresses.to_string()) |
527 | + smtp_chunk_ok!(250, 0, 0, 0, addresses.to_string()) |
528 | } |
529 | Err(err) => match err { |
530 | crate::expansion::Error::Server(message) => { |
531 | - smtp_err!( |
532 | + smtp_chunk_err!( |
533 | 500, |
534 | 0, |
535 | 0, |
536 | @@ -296,20 +288,26 @@ impl Session { |
537 | ) |
538 | } |
539 | crate::expansion::Error::NotFound(name) => { |
540 | - smtp_err!(500, 0, 0, 0, format!("No such mailing list: {}", name)) |
541 | + smtp_chunk_err!( |
542 | + 500, |
543 | + 0, |
544 | + 0, |
545 | + 0, |
546 | + format!("No such mailing list: {}", name) |
547 | + ) |
548 | } |
549 | }, |
550 | } |
551 | } else { |
552 | - smtp_err!(500, 0, 0, 0, "Server does not support EXPN") |
553 | + smtp_chunk_err!(500, 0, 0, 0, "Server does not support EXPN") |
554 | } |
555 | } |
556 | Request::Help { value } => { |
557 | self.check_initialized()?; |
558 | if value.is_empty() { |
559 | - smtp_ok!(250, 0, 0, 0, self.help_banner.to_string()) |
560 | + smtp_chunk_ok!(250, 0, 0, 0, self.help_banner.to_string()) |
561 | } else { |
562 | - smtp_ok!( |
563 | + smtp_chunk_ok!( |
564 | 250, |
565 | 0, |
566 | 0, |
567 | @@ -337,8 +335,8 @@ impl Session { |
568 | self.check_body(&message_payload)?; |
569 | let parser = MessageParser::new(); |
570 | let response = match parser.parse(&message_payload) { |
571 | - Some(_) => smtp_ok!(250, 0, 0, 0, "OK".to_string()), |
572 | - None => smtp_err!( |
573 | + Some(_) => smtp_chunk_ok!(250, 0, 0, 0, "OK".to_string()), |
574 | + None => smtp_chunk_err!( |
575 | 500, |
576 | 0, |
577 | 0, |
578 | @@ -354,7 +352,7 @@ impl Session { |
579 | } else { |
580 | tracing::info!("Initializing data transfer mode"); |
581 | self.data_transfer = Some(DataTransfer::Data); |
582 | - smtp_ok!( |
583 | + smtp_chunk_ok!( |
584 | 354, |
585 | 0, |
586 | 0, |
587 | @@ -366,9 +364,9 @@ impl Session { |
588 | Request::Rset => { |
589 | self.check_initialized()?; |
590 | self.reset(); |
591 | - smtp_ok!(200, 0, 0, 0, "".to_string()) |
592 | + smtp_chunk_ok!(200, 0, 0, 0, "".to_string()) |
593 | } |
594 | - Request::Quit => smtp_ok!(221, 0, 0, 0, "Ciao!".to_string()), |
595 | + Request::Quit => smtp_chunk_ok!(221, 0, 0, 0, "Ciao!".to_string()), |
596 | } |
597 | } |
598 | } |
599 | @@ -439,12 +437,12 @@ mod test { |
600 | host: EXAMPLE_HOSTNAME.to_string(), |
601 | }, |
602 | payload: None, |
603 | - expected: smtp_ok!(250, 0, 0, 0, String::from("Hello example.org")), |
604 | + expected: smtp_chunk_ok!(250, 0, 0, 0, String::from("Hello example.org")), |
605 | }, |
606 | TestCase { |
607 | request: Request::Quit {}, |
608 | payload: None, |
609 | - expected: smtp_ok!(221, 0, 0, 0, String::from("Ciao!")), |
610 | + expected: smtp_chunk_ok!(221, 0, 0, 0, String::from("Ciao!")), |
611 | }, |
612 | ]; |
613 | let mut session = Session::default(); |
614 | @@ -465,7 +463,7 @@ mod test { |
615 | }, |
616 | }, |
617 | payload: None, |
618 | - expected: smtp_err!(500, 0, 0, 0, String::from("It's polite to say EHLO first")), |
619 | + expected: smtp_chunk_err!(500, 0, 0, 0, String::from("It's polite to say EHLO first")), |
620 | }]; |
621 | let mut session = Session::default().our_hostname(EXAMPLE_HOSTNAME); |
622 | process_all(&mut session, requests); |
623 | @@ -481,12 +479,12 @@ mod test { |
624 | host: EXAMPLE_HOSTNAME.to_string(), |
625 | }, |
626 | payload: None, |
627 | - expected: smtp_ok!(250, 0, 0, 0, String::from("Hello example.org")), |
628 | + expected: smtp_chunk_ok!(250, 0, 0, 0, String::from("Hello example.org")), |
629 | }, |
630 | TestCase { |
631 | request: Request::Data {}, |
632 | payload: None, |
633 | - expected: smtp_ok!( |
634 | + expected: smtp_chunk_ok!( |
635 | 354, |
636 | 0, |
637 | 0, |
638 | @@ -502,7 +500,13 @@ mod test { |
639 | "# |
640 | .as_bytes(), |
641 | )), |
642 | - expected: smtp_err!(500, 0, 0, 0, "Non ascii characters found in message body"), |
643 | + expected: smtp_chunk_err!( |
644 | + 500, |
645 | + 0, |
646 | + 0, |
647 | + 0, |
648 | + "Non ascii characters found in message body" |
649 | + ), |
650 | }, |
651 | // upgrade the connection to extended mode |
652 | TestCase { |
653 | @@ -510,12 +514,12 @@ mod test { |
654 | host: EXAMPLE_HOSTNAME.to_string(), |
655 | }, |
656 | payload: None, |
657 | - expected: Ok(Response::Ehlo(expected_ehlo_response)), |
658 | + expected: Ok(Chunk(vec![Response::Ehlo(expected_ehlo_response)])), |
659 | }, |
660 | TestCase { |
661 | request: Request::Data {}, |
662 | payload: None, |
663 | - expected: smtp_ok!( |
664 | + expected: smtp_chunk_ok!( |
665 | 354, |
666 | 0, |
667 | 0, |
668 | @@ -531,7 +535,7 @@ mod test { |
669 | "# |
670 | .as_bytes(), |
671 | )), |
672 | - expected: smtp_ok!(250, 0, 0, 0, "OK"), |
673 | + expected: smtp_chunk_ok!(250, 0, 0, 0, "OK"), |
674 | }, |
675 | ]; |
676 | let mut session = Session::default() |
677 | @@ -548,7 +552,7 @@ mod test { |
678 | host: EXAMPLE_HOSTNAME.to_string(), |
679 | }, |
680 | payload: None, |
681 | - expected: smtp_ok!(250, 0, 0, 0, "Hello example.org"), |
682 | + expected: smtp_chunk_ok!(250, 0, 0, 0, "Hello example.org"), |
683 | }, |
684 | TestCase { |
685 | request: Request::Mail { |
686 | @@ -558,7 +562,7 @@ mod test { |
687 | }, |
688 | }, |
689 | payload: None, |
690 | - expected: smtp_ok!(250, 0, 0, 0, "OK"), |
691 | + expected: smtp_chunk_ok!(250, 0, 0, 0, "OK"), |
692 | }, |
693 | TestCase { |
694 | request: Request::Rcpt { |
695 | @@ -568,13 +572,13 @@ mod test { |
696 | }, |
697 | }, |
698 | payload: None, |
699 | - expected: smtp_ok!(250, 0, 0, 0, "OK"), |
700 | + expected: smtp_chunk_ok!(250, 0, 0, 0, "OK"), |
701 | }, |
702 | // initiate data transfer |
703 | TestCase { |
704 | request: Request::Data {}, |
705 | payload: None, |
706 | - expected: smtp_ok!( |
707 | + expected: smtp_chunk_ok!( |
708 | 354, |
709 | 0, |
710 | 0, |
711 | @@ -594,7 +598,7 @@ Note that it doesn't end with a "." since that parsing happens as part of the |
712 | transport rather than the session. |
713 | "#, |
714 | )), |
715 | - expected: smtp_ok!(250, 0, 0, 0, "OK"), |
716 | + expected: smtp_chunk_ok!(250, 0, 0, 0, "OK"), |
717 | }, |
718 | ]; |
719 | let mut session = Session::default().our_hostname(EXAMPLE_HOSTNAME); |