Skip to content

Commit

Permalink
Updated transparency procedure to escape <LF>. as well as <CR><LF>. t…
Browse files Browse the repository at this point in the history
…o prevent SMTP smuggling on vulnerable servers
  • Loading branch information
mdecimus committed Dec 28, 2023
1 parent d776716 commit 2ada376
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 18 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
mail-send 0.4.4
================================
- Updated transparency procedure to escape <LF>. as well as <CR><LF>. to prevent SMTP smuggling on vulnerable servers.

mail-send 0.4.3
================================
- Bump `rustls` dependency to 0.22
Expand Down
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>"]
license = "Apache-2.0 OR MIT"
Expand All @@ -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 }
Expand Down
22 changes: 8 additions & 14 deletions src/smtp/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,29 +102,23 @@ impl<T: AsyncRead + AsyncWrite + Unpin> SmtpClient<T> {

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..) {
Expand Down

0 comments on commit 2ada376

Please sign in to comment.