Author:
Hash:
Timestamp:
+163 -119 +/-4 browse
Kevin Schoon [me@kevinschoon.com]
3a444a06440a4c1380f9d212fa5a3657a127fb66
Mon, 12 Aug 2024 17:45:46 +0000 (1.2 years ago)
| 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); |