diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c00195..ee9df51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +mail-send 0.4.4 +================================ +- Updated transparency procedure to escape . as well as . to prevent SMTP smuggling on vulnerable servers. + mail-send 0.4.3 ================================ - Bump `rustls` dependency to 0.22 diff --git a/Cargo.toml b/Cargo.toml index 6edb01e..421d1ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mail-send" description = "E-mail delivery library with SMTP and DKIM support" -version = "0.4.3" +version = "0.4.4" edition = "2021" authors = [ "Stalwart Labs "] license = "Apache-2.0 OR MIT" @@ -15,9 +15,9 @@ readme = "README.md" doctest = false [dependencies] -smtp-proto = { version = "0.1", git = "https://github.com/stalwartlabs/smtp-proto" } -mail-auth = { version = "0.3", git = "https://github.com/stalwartlabs/mail-auth", optional = true } -mail-builder = { version = "0.3", git = "https://github.com/stalwartlabs/mail-builder", optional = true } +smtp-proto = { version = "0.1" } +mail-auth = { version = "0.3", optional = true } +mail-builder = { version = "0.3", optional = true } base64 = "0.21" rand = { version = "0.8.5", optional = true } md5 = { version = "0.7.0", optional = true } diff --git a/src/smtp/message.rs b/src/smtp/message.rs index 172d67b..8ea5c5f 100644 --- a/src/smtp/message.rs +++ b/src/smtp/message.rs @@ -102,29 +102,23 @@ impl SmtpClient { pub async fn write_message(&mut self, message: &[u8]) -> tokio::io::Result<()> { // Transparency procedure - #[derive(Debug)] - enum State { - Cr, - CrLf, - Init, - } + let mut is_lf = false; + + // As per RFC 5322bis, section 2.3: + // CR and LF MUST only occur together as CRLF; they MUST NOT appear + // independently in the body. - let mut state = State::Init; let mut last_pos = 0; for (pos, byte) in message.iter().enumerate() { - if *byte == b'.' && matches!(state, State::CrLf) { + if *byte == b'.' && is_lf { if let Some(bytes) = message.get(last_pos..pos) { self.stream.write_all(bytes).await?; self.stream.write_all(b".").await?; last_pos = pos; } - state = State::Init; - } else if *byte == b'\r' { - state = State::Cr; - } else if *byte == b'\n' && matches!(state, State::Cr) { - state = State::CrLf; + is_lf = false; } else { - state = State::Init; + is_lf = *byte == b'\n'; } } if let Some(bytes) = message.get(last_pos..) {