Author:
Hash:
Timestamp:
+84 -12 +/-2 browse
Kevin Schoon [me@kevinschoon.com]
883093f791e6e7ac8857863ca317335e49d6dd32
Fri, 30 Aug 2024 20:48:41 +0000 (1.2 years ago)
| 1 | diff --git a/maitred/src/server.rs b/maitred/src/server.rs |
| 2 | index 2f98398..282dc8e 100644 |
| 3 | --- a/maitred/src/server.rs |
| 4 | +++ b/maitred/src/server.rs |
| 5 | @@ -21,8 +21,8 @@ use tokio_util::codec::Framed; |
| 6 | |
| 7 | use crate::error::Error; |
| 8 | use crate::session::Session; |
| 9 | - use crate::transport::Command; |
| 10 | - use crate::transport::Transport; |
| 11 | + use crate::smtp_response; |
| 12 | + use crate::transport::{Command, Transport}; |
| 13 | use crate::worker::{Packet, Worker}; |
| 14 | use crate::{Delivery, Milter, Response, SmtpResponse}; |
| 15 | |
| 16 | @@ -190,13 +190,46 @@ where |
| 17 | } |
| 18 | } |
| 19 | Ok(Some(Err(err))) => { |
| 20 | - tracing::error!("Internal server error: {}", err); |
| 21 | + tracing::warn!("Client Error: {}", err); |
| 22 | let response = match err { |
| 23 | crate::transport::Error::PipelineNotEnabled => { |
| 24 | crate::smtp_response!(500, 0, 0, 0, "Pipelining is not enabled") |
| 25 | } |
| 26 | crate::transport::Error::Smtp(e) => { |
| 27 | - crate::smtp_response!(500, 0, 0, 0, e.to_string()) |
| 28 | + match e { |
| 29 | + smtp_proto::Error::NeedsMoreData { bytes_left: _ } => { |
| 30 | + // TODO |
| 31 | + smtp_response!(500, 0, 0, 0, e.to_string()) |
| 32 | + } |
| 33 | + smtp_proto::Error::UnknownCommand => { |
| 34 | + smtp_response!(500, 5, 5, 1, "Invalid Command") |
| 35 | + } |
| 36 | + smtp_proto::Error::InvalidSenderAddress => { |
| 37 | + smtp_response!(501, 5, 1, 8, e.to_string()) |
| 38 | + } |
| 39 | + smtp_proto::Error::InvalidRecipientAddress => { |
| 40 | + smtp_response!(501, 5, 1, 3, e.to_string()) |
| 41 | + } |
| 42 | + smtp_proto::Error::SyntaxError { syntax: _ } => { |
| 43 | + smtp_response!(501, 5, 5, 2, e.to_string()) |
| 44 | + } |
| 45 | + smtp_proto::Error::InvalidParameter { param: _ } => { |
| 46 | + // TODO |
| 47 | + smtp_response!(500, 0, 0, 0, e.to_string()) |
| 48 | + } |
| 49 | + smtp_proto::Error::UnsupportedParameter { param: _ } => { |
| 50 | + // TODO |
| 51 | + smtp_response!(500, 0, 0, 0, e.to_string()) |
| 52 | + } |
| 53 | + smtp_proto::Error::ResponseTooLong => { |
| 54 | + // TODO |
| 55 | + smtp_response!(500, 0, 0, 0, e.to_string()) |
| 56 | + } |
| 57 | + smtp_proto::Error::InvalidResponse { code: _ } => { |
| 58 | + // TODO |
| 59 | + smtp_response!(500, 0, 0, 0, e.to_string()) |
| 60 | + } |
| 61 | + } |
| 62 | } |
| 63 | // IO Errors considered fatal for the entire session |
| 64 | crate::transport::Error::Io(e) => return Err(Error::Io(e)), |
| 65 | @@ -274,7 +307,12 @@ where |
| 66 | let (socket, _) = listener.accept().await.unwrap(); |
| 67 | let addr = socket.local_addr()?; |
| 68 | tracing::info!("Accepted connection on: {:?}", addr); |
| 69 | - let framed = Framed::new(socket, Transport::default().pipelining(true)); |
| 70 | + let pipelining = self |
| 71 | + .options |
| 72 | + .as_ref() |
| 73 | + .is_some_and(|opts| opts.capabilities & smtp_proto::EXT_PIPELINING != 0) |
| 74 | + || self.options.is_none(); |
| 75 | + let framed = Framed::new(socket, Transport::default().pipelining(pipelining)); |
| 76 | |
| 77 | match self.process(framed, global_queue.clone()).await { |
| 78 | Ok(_) => { |
| 79 | @@ -291,7 +329,7 @@ where |
| 80 | #[cfg(test)] |
| 81 | mod test { |
| 82 | |
| 83 | - use crate::{Delivery, Milter}; |
| 84 | + use crate::{Delivery, Milter, SessionOptions}; |
| 85 | |
| 86 | use super::*; |
| 87 | |
| 88 | @@ -349,6 +387,40 @@ mod test { |
| 89 | todo!() |
| 90 | } |
| 91 | } |
| 92 | + #[tokio::test] |
| 93 | + async fn test_server() { |
| 94 | + let stream = FakeStream { |
| 95 | + buffer: vec![ |
| 96 | + "HELO example.org\r\n".into(), |
| 97 | + "MAIL FROM: <fuu@bar.com>\r\n".into(), |
| 98 | + "RCPT TO: <baz@qux.com>\r\n".into(), |
| 99 | + "DATA\r\n".into(), |
| 100 | + "Subject: Hello World\r\n.\r\n".into(), |
| 101 | + "QUIT\r\n".into(), |
| 102 | + ], |
| 103 | + ..Default::default() |
| 104 | + }; |
| 105 | + let server = Server::default() |
| 106 | + // turn off all extended capabilities |
| 107 | + .with_session_opts(SessionOptions::default().capabilities(0)) |
| 108 | + .with_milter(Milter::new(|_: Message<'static>| { |
| 109 | + Box::pin(async move { Ok(Message::default().into_owned()) }) |
| 110 | + })) |
| 111 | + .with_delivery(Delivery::new(|_: Message<'static>| { |
| 112 | + Box::pin(async move { Ok(()) }) |
| 113 | + })); |
| 114 | + let framed = Framed::new(stream, Transport::default()); |
| 115 | + let global_queue = Arc::new(Injector::<Packet>::new()); |
| 116 | + server.process(framed, global_queue.clone()).await.unwrap(); |
| 117 | + let packet = global_queue.steal().success().unwrap(); |
| 118 | + assert!(packet |
| 119 | + .mail_from |
| 120 | + .as_ref() |
| 121 | + .is_some_and(|mail_from| mail_from.email() == "fuu@bar.com")); |
| 122 | + assert!(packet.rcpt_to.as_ref().is_some_and(|rcpts| rcpts |
| 123 | + .first() |
| 124 | + .is_some_and(|rcpt_to| rcpt_to.email() == "baz@qux.com"))); |
| 125 | + } |
| 126 | |
| 127 | #[tokio::test] |
| 128 | async fn test_server_pipelined() { |
| 129 | diff --git a/maitred/src/session.rs b/maitred/src/session.rs |
| 130 | index c78c92c..5c1a081 100644 |
| 131 | --- a/maitred/src/session.rs |
| 132 | +++ b/maitred/src/session.rs |
| 133 | @@ -195,9 +195,9 @@ impl Session { |
| 134 | if self.initialized.is_none() { |
| 135 | return Err(smtp_response!( |
| 136 | 500, |
| 137 | - 0, |
| 138 | - 0, |
| 139 | - 0, |
| 140 | + 5, |
| 141 | + 5, |
| 142 | + 1, |
| 143 | "It's polite to say EHLO first" |
| 144 | )); |
| 145 | } |
| 146 | @@ -558,9 +558,9 @@ mod test { |
| 147 | payload: None, |
| 148 | expected: Err(smtp_response!( |
| 149 | 500, |
| 150 | - 0, |
| 151 | - 0, |
| 152 | - 0, |
| 153 | + 5, |
| 154 | + 5, |
| 155 | + 1, |
| 156 | String::from("It's polite to say EHLO first") |
| 157 | )), |
| 158 | }]; |