Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c9fc6cb71 | ||
|
|
2228cbdf93 | ||
|
|
17c95b0fa8 | ||
|
|
62725af00a | ||
|
|
758bf1a4a7 | ||
|
|
054c79f914 | ||
|
|
985fa7edc4 |
7
.github/workflows/test.yml
vendored
7
.github/workflows/test.yml
vendored
@@ -133,9 +133,12 @@ jobs:
|
|||||||
- name: Test with default features
|
- name: Test with default features
|
||||||
run: cargo test
|
run: cargo test
|
||||||
|
|
||||||
- name: Test with all features
|
- name: Test with all features (-native-tls)
|
||||||
run: cargo test --all-features
|
run: cargo test --no-default-features --features async-std,async-std1,async-std1-rustls-tls,async-trait,base64,boring,boring-tls,builder,dkim,ed25519-dalek,email-encoding,fastrand,file-transport,file-transport-envelope,futures-io,futures-rustls,futures-util,hostname,httpdate,mime,mime03,nom,once_cell,pool,quoted_printable,regex,rsa,rustls,rustls-pemfile,rustls-tls,sendmail-transport,serde,serde_json,sha2,smtp-transport,socket2,tokio1,tokio1-boring-tls,tokio1-rustls-tls,tokio1_boring,tokio1_crate,tokio1_rustls,tracing,uuid,webpki-roots
|
||||||
|
|
||||||
|
- name: Test with all features (-boring-tls)
|
||||||
|
run: cargo test --no-default-features --features async-std,async-std1,async-std1-rustls-tls,async-trait,base64,builder,dkim,ed25519-dalek,email-encoding,fastrand,file-transport,file-transport-envelope,futures-io,futures-rustls,futures-util,hostname,httpdate,mime,mime03,native-tls,nom,once_cell,pool,quoted_printable,regex,rsa,rustls,rustls-pemfile,rustls-tls,sendmail-transport,serde,serde_json,sha2,smtp-transport,socket2,tokio1,tokio1-native-tls,tokio1-rustls-tls,tokio1_crate,tokio1_native_tls_crate,tokio1_rustls,tracing,uuid,webpki-roots
|
||||||
|
|
||||||
# coverage:
|
# coverage:
|
||||||
# name: Coverage
|
# name: Coverage
|
||||||
# runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
|
|||||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -1,5 +1,25 @@
|
|||||||
|
<a name="v0.10.1"></a>
|
||||||
|
### v0.10.1 (2022-07-20)
|
||||||
|
|
||||||
|
#### Features
|
||||||
|
|
||||||
|
* Add `boring-tls` support for `SmtpTransport` and `AsyncSmtpTransport`. The latter is only supported with the tokio runtime. ([#797]) ([#798])
|
||||||
|
* Make the minimum TLS version configurable. ([#799]) ([#800])
|
||||||
|
|
||||||
|
#### Bug Fixes
|
||||||
|
|
||||||
|
* Ensure connections are closed on abort. ([#801])
|
||||||
|
* Fix SMTP dot stuffing. ([#803])
|
||||||
|
|
||||||
|
[#797]: https://github.com/lettre/lettre/pull/797
|
||||||
|
[#798]: https://github.com/lettre/lettre/pull/798
|
||||||
|
[#799]: https://github.com/lettre/lettre/pull/799
|
||||||
|
[#800]: https://github.com/lettre/lettre/pull/800
|
||||||
|
[#801]: https://github.com/lettre/lettre/pull/801
|
||||||
|
[#803]: https://github.com/lettre/lettre/pull/803
|
||||||
|
|
||||||
<a name="v0.10.0"></a>
|
<a name="v0.10.0"></a>
|
||||||
### v0.10.0 (unreleased)
|
### v0.10.0 (2022-06-29)
|
||||||
|
|
||||||
#### Upgrade notes
|
#### Upgrade notes
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lettre"
|
name = "lettre"
|
||||||
# remember to update html_root_url and README.md (Cargo.toml example and deps.rs badge)
|
# remember to update html_root_url and README.md (Cargo.toml example and deps.rs badge)
|
||||||
version = "0.10.0"
|
version = "0.10.1"
|
||||||
description = "Email client"
|
description = "Email client"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
homepage = "https://lettre.rs"
|
homepage = "https://lettre.rs"
|
||||||
@@ -46,6 +46,7 @@ native-tls = { version = "0.2", optional = true } # feature
|
|||||||
rustls = { version = "0.20", features = ["dangerous_configuration"], optional = true }
|
rustls = { version = "0.20", features = ["dangerous_configuration"], optional = true }
|
||||||
rustls-pemfile = { version = "1", optional = true }
|
rustls-pemfile = { version = "1", optional = true }
|
||||||
webpki-roots = { version = "0.22", optional = true }
|
webpki-roots = { version = "0.22", optional = true }
|
||||||
|
boring = { version = "2.0.0", optional = true }
|
||||||
|
|
||||||
# async
|
# async
|
||||||
futures-io = { version = "0.3.7", optional = true }
|
futures-io = { version = "0.3.7", optional = true }
|
||||||
@@ -61,6 +62,7 @@ futures-rustls = { version = "0.22", optional = true }
|
|||||||
tokio1_crate = { package = "tokio", version = "1", features = ["fs", "rt", "process", "time", "net", "io-util"], optional = true }
|
tokio1_crate = { package = "tokio", version = "1", features = ["fs", "rt", "process", "time", "net", "io-util"], optional = true }
|
||||||
tokio1_native_tls_crate = { package = "tokio-native-tls", version = "0.3", optional = true }
|
tokio1_native_tls_crate = { package = "tokio-native-tls", version = "0.3", optional = true }
|
||||||
tokio1_rustls = { package = "tokio-rustls", version = "0.23", optional = true }
|
tokio1_rustls = { package = "tokio-rustls", version = "0.23", optional = true }
|
||||||
|
tokio1_boring = { package = "tokio-boring", version = "2.1.4", optional = true }
|
||||||
|
|
||||||
## dkim
|
## dkim
|
||||||
sha2 = { version = "0.10", optional = true }
|
sha2 = { version = "0.10", optional = true }
|
||||||
@@ -102,6 +104,8 @@ pool = ["futures-util"]
|
|||||||
|
|
||||||
rustls-tls = ["webpki-roots", "rustls", "rustls-pemfile"]
|
rustls-tls = ["webpki-roots", "rustls", "rustls-pemfile"]
|
||||||
|
|
||||||
|
boring-tls = ["boring"]
|
||||||
|
|
||||||
# async
|
# async
|
||||||
async-std1 = ["async-std", "async-trait", "futures-io", "futures-util"]
|
async-std1 = ["async-std", "async-trait", "futures-io", "futures-util"]
|
||||||
#async-std1-native-tls = ["async-std1", "native-tls", "async-native-tls"]
|
#async-std1-native-tls = ["async-std1", "native-tls", "async-native-tls"]
|
||||||
@@ -109,6 +113,7 @@ async-std1-rustls-tls = ["async-std1", "rustls-tls", "futures-rustls"]
|
|||||||
tokio1 = ["tokio1_crate", "async-trait", "futures-io", "futures-util"]
|
tokio1 = ["tokio1_crate", "async-trait", "futures-io", "futures-util"]
|
||||||
tokio1-native-tls = ["tokio1", "native-tls", "tokio1_native_tls_crate"]
|
tokio1-native-tls = ["tokio1", "native-tls", "tokio1_native_tls_crate"]
|
||||||
tokio1-rustls-tls = ["tokio1", "rustls-tls", "tokio1_rustls"]
|
tokio1-rustls-tls = ["tokio1", "rustls-tls", "tokio1_rustls"]
|
||||||
|
tokio1-boring-tls = ["tokio1", "boring-tls", "tokio1_boring"]
|
||||||
|
|
||||||
dkim = ["base64", "sha2", "rsa", "ed25519-dalek", "regex", "once_cell"]
|
dkim = ["base64", "sha2", "rsa", "ed25519-dalek", "regex", "once_cell"]
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://deps.rs/crate/lettre/0.10.0">
|
<a href="https://deps.rs/crate/lettre/0.10.1">
|
||||||
<img src="https://deps.rs/crate/lettre/0.10.0/status.svg"
|
<img src="https://deps.rs/crate/lettre/0.10.1/status.svg"
|
||||||
alt="dependency status" />
|
alt="dependency status" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
24
src/lib.rs
24
src/lib.rs
@@ -41,6 +41,15 @@
|
|||||||
//!
|
//!
|
||||||
//! NOTE: native-tls isn't supported with `async-std`
|
//! NOTE: native-tls isn't supported with `async-std`
|
||||||
//!
|
//!
|
||||||
|
//! #### SMTP over TLS via the boring crate (Boring TLS)
|
||||||
|
//!
|
||||||
|
//! _Secure SMTP connections using TLS from the `boring-tls` crate_
|
||||||
|
//!
|
||||||
|
//! * **boring-tls**: TLS support for the synchronous version of the API
|
||||||
|
//! * **tokio1-boring-tls**: TLS support for the `tokio1` async version of the API
|
||||||
|
//!
|
||||||
|
//! NOTE: boring-tls isn't supported with `async-std`
|
||||||
|
//!
|
||||||
//! #### SMTP over TLS via the rustls crate
|
//! #### SMTP over TLS via the rustls crate
|
||||||
//!
|
//!
|
||||||
//! _Secure SMTP connections using TLS from the `rustls-tls` crate_
|
//! _Secure SMTP connections using TLS from the `rustls-tls` crate_
|
||||||
@@ -100,7 +109,7 @@
|
|||||||
//! [mime 0.3]: https://docs.rs/mime/0.3
|
//! [mime 0.3]: https://docs.rs/mime/0.3
|
||||||
//! [DKIM]: https://datatracker.ietf.org/doc/html/rfc6376
|
//! [DKIM]: https://datatracker.ietf.org/doc/html/rfc6376
|
||||||
|
|
||||||
#![doc(html_root_url = "https://docs.rs/crate/lettre/0.10.0")]
|
#![doc(html_root_url = "https://docs.rs/crate/lettre/0.10.1")]
|
||||||
#![doc(html_favicon_url = "https://lettre.rs/favicon.ico")]
|
#![doc(html_favicon_url = "https://lettre.rs/favicon.ico")]
|
||||||
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/15113230?v=4")]
|
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/15113230?v=4")]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
@@ -132,6 +141,10 @@
|
|||||||
|
|
||||||
#[cfg(not(lettre_ignore_tls_mismatch))]
|
#[cfg(not(lettre_ignore_tls_mismatch))]
|
||||||
mod compiletime_checks {
|
mod compiletime_checks {
|
||||||
|
#[cfg(all(feature = "native-tls", feature = "boring-tls"))]
|
||||||
|
compile_error!("feature \"native-tls\" and feature \"boring-tls\" cannot be enabled at the same time, otherwise
|
||||||
|
the executable will fail to link.");
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "tokio1",
|
feature = "tokio1",
|
||||||
feature = "native-tls",
|
feature = "native-tls",
|
||||||
@@ -150,6 +163,15 @@ mod compiletime_checks {
|
|||||||
If you'd like to use `native-tls` make sure that the `rustls-tls` feature hasn't been enabled by mistake.
|
If you'd like to use `native-tls` make sure that the `rustls-tls` feature hasn't been enabled by mistake.
|
||||||
Make sure to apply the same to any of your crate dependencies that use the `lettre` crate.");
|
Make sure to apply the same to any of your crate dependencies that use the `lettre` crate.");
|
||||||
|
|
||||||
|
#[cfg(all(
|
||||||
|
feature = "tokio1",
|
||||||
|
feature = "boring-tls",
|
||||||
|
not(feature = "tokio1-boring-tls")
|
||||||
|
))]
|
||||||
|
compile_error!("Lettre is being built with the `tokio1` and the `boring-tls` features, but the `tokio1-boring-tls` feature hasn't been turned on.
|
||||||
|
If you'd like to use `boring-tls` make sure that the `rustls-tls` feature hasn't been enabled by mistake.
|
||||||
|
Make sure to apply the same to any of your crate dependencies that use the `lettre` crate.");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "async-std1",
|
feature = "async-std1",
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ impl AsyncSmtpConnection {
|
|||||||
self.panic = true;
|
self.panic = true;
|
||||||
let _ = self.command(Quit).await;
|
let _ = self.command(Quit).await;
|
||||||
}
|
}
|
||||||
|
let _ = self.stream.close().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the underlying stream
|
/// Sets the underlying stream
|
||||||
@@ -320,7 +321,7 @@ impl AsyncSmtpConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The X509 certificate of the server (DER encoded)
|
/// The X509 certificate of the server (DER encoded)
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
pub fn peer_certificate(&self) -> Result<Vec<u8>, Error> {
|
pub fn peer_certificate(&self) -> Result<Vec<u8>, Error> {
|
||||||
self.stream.get_ref().peer_certificate()
|
self.stream.get_ref().peer_certificate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ use futures_io::{
|
|||||||
};
|
};
|
||||||
#[cfg(feature = "async-std1-rustls-tls")]
|
#[cfg(feature = "async-std1-rustls-tls")]
|
||||||
use futures_rustls::client::TlsStream as AsyncStd1RustlsTlsStream;
|
use futures_rustls::client::TlsStream as AsyncStd1RustlsTlsStream;
|
||||||
|
#[cfg(feature = "tokio1-boring-tls")]
|
||||||
|
use tokio1_boring::SslStream as Tokio1SslStream;
|
||||||
#[cfg(feature = "tokio1")]
|
#[cfg(feature = "tokio1")]
|
||||||
use tokio1_crate::io::{AsyncRead as _, AsyncWrite as _, ReadBuf as Tokio1ReadBuf};
|
use tokio1_crate::io::{AsyncRead as _, AsyncWrite as _, ReadBuf as Tokio1ReadBuf};
|
||||||
#[cfg(feature = "tokio1")]
|
#[cfg(feature = "tokio1")]
|
||||||
@@ -31,6 +33,7 @@ use tokio1_rustls::client::TlsStream as Tokio1RustlsTlsStream;
|
|||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
feature = "tokio1-native-tls",
|
feature = "tokio1-native-tls",
|
||||||
feature = "tokio1-rustls-tls",
|
feature = "tokio1-rustls-tls",
|
||||||
|
feature = "tokio1-boring-tls",
|
||||||
feature = "async-std1-native-tls",
|
feature = "async-std1-native-tls",
|
||||||
feature = "async-std1-rustls-tls"
|
feature = "async-std1-rustls-tls"
|
||||||
))]
|
))]
|
||||||
@@ -60,6 +63,9 @@ enum InnerAsyncNetworkStream {
|
|||||||
/// Encrypted Tokio 1.x TCP stream
|
/// Encrypted Tokio 1.x TCP stream
|
||||||
#[cfg(feature = "tokio1-rustls-tls")]
|
#[cfg(feature = "tokio1-rustls-tls")]
|
||||||
Tokio1RustlsTls(Tokio1RustlsTlsStream<Tokio1TcpStream>),
|
Tokio1RustlsTls(Tokio1RustlsTlsStream<Tokio1TcpStream>),
|
||||||
|
/// Encrypted Tokio 1.x TCP stream
|
||||||
|
#[cfg(feature = "tokio1-boring-tls")]
|
||||||
|
Tokio1BoringTls(Tokio1SslStream<Tokio1TcpStream>),
|
||||||
/// Plain Tokio 1.x TCP stream
|
/// Plain Tokio 1.x TCP stream
|
||||||
#[cfg(feature = "async-std1")]
|
#[cfg(feature = "async-std1")]
|
||||||
AsyncStd1Tcp(AsyncStd1TcpStream),
|
AsyncStd1Tcp(AsyncStd1TcpStream),
|
||||||
@@ -93,6 +99,8 @@ impl AsyncNetworkStream {
|
|||||||
}
|
}
|
||||||
#[cfg(feature = "tokio1-rustls-tls")]
|
#[cfg(feature = "tokio1-rustls-tls")]
|
||||||
InnerAsyncNetworkStream::Tokio1RustlsTls(ref s) => s.get_ref().0.peer_addr(),
|
InnerAsyncNetworkStream::Tokio1RustlsTls(ref s) => s.get_ref().0.peer_addr(),
|
||||||
|
#[cfg(feature = "tokio1-boring-tls")]
|
||||||
|
InnerAsyncNetworkStream::Tokio1BoringTls(ref s) => s.get_ref().peer_addr(),
|
||||||
#[cfg(feature = "async-std1")]
|
#[cfg(feature = "async-std1")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1Tcp(ref s) => s.peer_addr(),
|
InnerAsyncNetworkStream::AsyncStd1Tcp(ref s) => s.peer_addr(),
|
||||||
#[cfg(feature = "async-std1-native-tls")]
|
#[cfg(feature = "async-std1-native-tls")]
|
||||||
@@ -229,14 +237,22 @@ impl AsyncNetworkStream {
|
|||||||
match &self.inner {
|
match &self.inner {
|
||||||
#[cfg(all(
|
#[cfg(all(
|
||||||
feature = "tokio1",
|
feature = "tokio1",
|
||||||
not(any(feature = "tokio1-native-tls", feature = "tokio1-rustls-tls"))
|
not(any(
|
||||||
|
feature = "tokio1-native-tls",
|
||||||
|
feature = "tokio1-rustls-tls",
|
||||||
|
feature = "tokio1-boring-tls"
|
||||||
|
))
|
||||||
))]
|
))]
|
||||||
InnerAsyncNetworkStream::Tokio1Tcp(_) => {
|
InnerAsyncNetworkStream::Tokio1Tcp(_) => {
|
||||||
let _ = tls_parameters;
|
let _ = tls_parameters;
|
||||||
panic!("Trying to upgrade an AsyncNetworkStream without having enabled either the tokio1-native-tls or the tokio1-rustls-tls feature");
|
panic!("Trying to upgrade an AsyncNetworkStream without having enabled either the tokio1-native-tls or the tokio1-rustls-tls feature");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "tokio1-native-tls", feature = "tokio1-rustls-tls"))]
|
#[cfg(any(
|
||||||
|
feature = "tokio1-native-tls",
|
||||||
|
feature = "tokio1-rustls-tls",
|
||||||
|
feature = "tokio1-boring-tls"
|
||||||
|
))]
|
||||||
InnerAsyncNetworkStream::Tokio1Tcp(_) => {
|
InnerAsyncNetworkStream::Tokio1Tcp(_) => {
|
||||||
// get owned TcpStream
|
// get owned TcpStream
|
||||||
let tcp_stream = mem::replace(&mut self.inner, InnerAsyncNetworkStream::None);
|
let tcp_stream = mem::replace(&mut self.inner, InnerAsyncNetworkStream::None);
|
||||||
@@ -278,7 +294,11 @@ impl AsyncNetworkStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
#[cfg(any(feature = "tokio1-native-tls", feature = "tokio1-rustls-tls"))]
|
#[cfg(any(
|
||||||
|
feature = "tokio1-native-tls",
|
||||||
|
feature = "tokio1-rustls-tls",
|
||||||
|
feature = "tokio1-boring-tls"
|
||||||
|
))]
|
||||||
async fn upgrade_tokio1_tls(
|
async fn upgrade_tokio1_tls(
|
||||||
tcp_stream: Tokio1TcpStream,
|
tcp_stream: Tokio1TcpStream,
|
||||||
tls_parameters: TlsParameters,
|
tls_parameters: TlsParameters,
|
||||||
@@ -324,11 +344,31 @@ impl AsyncNetworkStream {
|
|||||||
Ok(InnerAsyncNetworkStream::Tokio1RustlsTls(stream))
|
Ok(InnerAsyncNetworkStream::Tokio1RustlsTls(stream))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
InnerTlsParameters::BoringTls(connector) => {
|
||||||
|
#[cfg(not(feature = "tokio1-boring-tls"))]
|
||||||
|
panic!("built without the tokio1-boring-tls feature");
|
||||||
|
|
||||||
|
#[cfg(feature = "tokio1-boring-tls")]
|
||||||
|
return {
|
||||||
|
let mut config = connector.configure().map_err(error::connection)?;
|
||||||
|
config.set_verify_hostname(tls_parameters.accept_invalid_hostnames);
|
||||||
|
|
||||||
|
let stream = tokio1_boring::connect(config, &domain, tcp_stream)
|
||||||
|
.await
|
||||||
|
.map_err(error::connection)?;
|
||||||
|
Ok(InnerAsyncNetworkStream::Tokio1BoringTls(stream))
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
#[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))]
|
#[cfg(any(
|
||||||
|
feature = "async-std1-native-tls",
|
||||||
|
feature = "async-std1-rustls-tls",
|
||||||
|
feature = "async-std1-boring-tls"
|
||||||
|
))]
|
||||||
async fn upgrade_asyncstd1_tls(
|
async fn upgrade_asyncstd1_tls(
|
||||||
tcp_stream: AsyncStd1TcpStream,
|
tcp_stream: AsyncStd1TcpStream,
|
||||||
mut tls_parameters: TlsParameters,
|
mut tls_parameters: TlsParameters,
|
||||||
@@ -377,6 +417,10 @@ impl AsyncNetworkStream {
|
|||||||
Ok(InnerAsyncNetworkStream::AsyncStd1RustlsTls(stream))
|
Ok(InnerAsyncNetworkStream::AsyncStd1RustlsTls(stream))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
InnerTlsParameters::BoringTls(connector) => {
|
||||||
|
panic!("boring-tls isn't supported with async-std yet.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,6 +432,8 @@ impl AsyncNetworkStream {
|
|||||||
InnerAsyncNetworkStream::Tokio1NativeTls(_) => true,
|
InnerAsyncNetworkStream::Tokio1NativeTls(_) => true,
|
||||||
#[cfg(feature = "tokio1-rustls-tls")]
|
#[cfg(feature = "tokio1-rustls-tls")]
|
||||||
InnerAsyncNetworkStream::Tokio1RustlsTls(_) => true,
|
InnerAsyncNetworkStream::Tokio1RustlsTls(_) => true,
|
||||||
|
#[cfg(feature = "tokio1-boring-tls")]
|
||||||
|
InnerAsyncNetworkStream::Tokio1BoringTls(_) => true,
|
||||||
#[cfg(feature = "async-std1")]
|
#[cfg(feature = "async-std1")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => false,
|
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => false,
|
||||||
#[cfg(feature = "async-std1-native-tls")]
|
#[cfg(feature = "async-std1-native-tls")]
|
||||||
@@ -422,6 +468,13 @@ impl AsyncNetworkStream {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.clone()
|
.clone()
|
||||||
.0),
|
.0),
|
||||||
|
#[cfg(feature = "tokio1-boring-tls")]
|
||||||
|
InnerAsyncNetworkStream::Tokio1BoringTls(stream) => Ok(stream
|
||||||
|
.ssl()
|
||||||
|
.peer_certificate()
|
||||||
|
.unwrap()
|
||||||
|
.to_der()
|
||||||
|
.map_err(error::tls)?),
|
||||||
#[cfg(feature = "async-std1")]
|
#[cfg(feature = "async-std1")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => {
|
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => {
|
||||||
Err(error::client("Connection is not encrypted"))
|
Err(error::client("Connection is not encrypted"))
|
||||||
@@ -477,6 +530,15 @@ impl FuturesAsyncRead for AsyncNetworkStream {
|
|||||||
Poll::Pending => Poll::Pending,
|
Poll::Pending => Poll::Pending,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "tokio1-boring-tls")]
|
||||||
|
InnerAsyncNetworkStream::Tokio1BoringTls(ref mut s) => {
|
||||||
|
let mut b = Tokio1ReadBuf::new(buf);
|
||||||
|
match Pin::new(s).poll_read(cx, &mut b) {
|
||||||
|
Poll::Ready(Ok(())) => Poll::Ready(Ok(b.filled().len())),
|
||||||
|
Poll::Ready(Err(err)) => Poll::Ready(Err(err)),
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
#[cfg(feature = "async-std1")]
|
#[cfg(feature = "async-std1")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1Tcp(ref mut s) => Pin::new(s).poll_read(cx, buf),
|
InnerAsyncNetworkStream::AsyncStd1Tcp(ref mut s) => Pin::new(s).poll_read(cx, buf),
|
||||||
#[cfg(feature = "async-std1-native-tls")]
|
#[cfg(feature = "async-std1-native-tls")]
|
||||||
@@ -508,6 +570,8 @@ impl FuturesAsyncWrite for AsyncNetworkStream {
|
|||||||
InnerAsyncNetworkStream::Tokio1NativeTls(ref mut s) => Pin::new(s).poll_write(cx, buf),
|
InnerAsyncNetworkStream::Tokio1NativeTls(ref mut s) => Pin::new(s).poll_write(cx, buf),
|
||||||
#[cfg(feature = "tokio1-rustls-tls")]
|
#[cfg(feature = "tokio1-rustls-tls")]
|
||||||
InnerAsyncNetworkStream::Tokio1RustlsTls(ref mut s) => Pin::new(s).poll_write(cx, buf),
|
InnerAsyncNetworkStream::Tokio1RustlsTls(ref mut s) => Pin::new(s).poll_write(cx, buf),
|
||||||
|
#[cfg(feature = "tokio1-boring-tls")]
|
||||||
|
InnerAsyncNetworkStream::Tokio1BoringTls(ref mut s) => Pin::new(s).poll_write(cx, buf),
|
||||||
#[cfg(feature = "async-std1")]
|
#[cfg(feature = "async-std1")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1Tcp(ref mut s) => Pin::new(s).poll_write(cx, buf),
|
InnerAsyncNetworkStream::AsyncStd1Tcp(ref mut s) => Pin::new(s).poll_write(cx, buf),
|
||||||
#[cfg(feature = "async-std1-native-tls")]
|
#[cfg(feature = "async-std1-native-tls")]
|
||||||
@@ -533,6 +597,8 @@ impl FuturesAsyncWrite for AsyncNetworkStream {
|
|||||||
InnerAsyncNetworkStream::Tokio1NativeTls(ref mut s) => Pin::new(s).poll_flush(cx),
|
InnerAsyncNetworkStream::Tokio1NativeTls(ref mut s) => Pin::new(s).poll_flush(cx),
|
||||||
#[cfg(feature = "tokio1-rustls-tls")]
|
#[cfg(feature = "tokio1-rustls-tls")]
|
||||||
InnerAsyncNetworkStream::Tokio1RustlsTls(ref mut s) => Pin::new(s).poll_flush(cx),
|
InnerAsyncNetworkStream::Tokio1RustlsTls(ref mut s) => Pin::new(s).poll_flush(cx),
|
||||||
|
#[cfg(feature = "tokio1-boring-tls")]
|
||||||
|
InnerAsyncNetworkStream::Tokio1BoringTls(ref mut s) => Pin::new(s).poll_flush(cx),
|
||||||
#[cfg(feature = "async-std1")]
|
#[cfg(feature = "async-std1")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1Tcp(ref mut s) => Pin::new(s).poll_flush(cx),
|
InnerAsyncNetworkStream::AsyncStd1Tcp(ref mut s) => Pin::new(s).poll_flush(cx),
|
||||||
#[cfg(feature = "async-std1-native-tls")]
|
#[cfg(feature = "async-std1-native-tls")]
|
||||||
@@ -554,6 +620,8 @@ impl FuturesAsyncWrite for AsyncNetworkStream {
|
|||||||
InnerAsyncNetworkStream::Tokio1NativeTls(ref mut s) => Pin::new(s).poll_shutdown(cx),
|
InnerAsyncNetworkStream::Tokio1NativeTls(ref mut s) => Pin::new(s).poll_shutdown(cx),
|
||||||
#[cfg(feature = "tokio1-rustls-tls")]
|
#[cfg(feature = "tokio1-rustls-tls")]
|
||||||
InnerAsyncNetworkStream::Tokio1RustlsTls(ref mut s) => Pin::new(s).poll_shutdown(cx),
|
InnerAsyncNetworkStream::Tokio1RustlsTls(ref mut s) => Pin::new(s).poll_shutdown(cx),
|
||||||
|
#[cfg(feature = "tokio1-boring-tls")]
|
||||||
|
InnerAsyncNetworkStream::Tokio1BoringTls(ref mut s) => Pin::new(s).poll_shutdown(cx),
|
||||||
#[cfg(feature = "async-std1")]
|
#[cfg(feature = "async-std1")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1Tcp(ref mut s) => Pin::new(s).poll_close(cx),
|
InnerAsyncNetworkStream::AsyncStd1Tcp(ref mut s) => Pin::new(s).poll_close(cx),
|
||||||
#[cfg(feature = "async-std1-native-tls")]
|
#[cfg(feature = "async-std1-native-tls")]
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ impl SmtpConnection {
|
|||||||
hello_name: &ClientId,
|
hello_name: &ClientId,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if self.server_info.supports_feature(Extension::StartTls) {
|
if self.server_info.supports_feature(Extension::StartTls) {
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
{
|
{
|
||||||
try_smtp!(self.command(Starttls), self);
|
try_smtp!(self.command(Starttls), self);
|
||||||
self.stream.get_mut().upgrade_tls(tls_parameters)?;
|
self.stream.get_mut().upgrade_tls(tls_parameters)?;
|
||||||
@@ -153,7 +153,11 @@ impl SmtpConnection {
|
|||||||
try_smtp!(self.ehlo(hello_name), self);
|
try_smtp!(self.ehlo(hello_name), self);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[cfg(not(any(feature = "native-tls", feature = "rustls-tls")))]
|
#[cfg(not(any(
|
||||||
|
feature = "native-tls",
|
||||||
|
feature = "rustls-tls",
|
||||||
|
feature = "boring-tls"
|
||||||
|
)))]
|
||||||
// This should never happen as `Tls` can only be created
|
// This should never happen as `Tls` can only be created
|
||||||
// when a TLS library is enabled
|
// when a TLS library is enabled
|
||||||
unreachable!("TLS support required but not supported");
|
unreachable!("TLS support required but not supported");
|
||||||
@@ -179,6 +183,7 @@ impl SmtpConnection {
|
|||||||
self.panic = true;
|
self.panic = true;
|
||||||
let _ = self.command(Quit);
|
let _ = self.command(Quit);
|
||||||
}
|
}
|
||||||
|
let _ = self.stream.get_mut().shutdown(std::net::Shutdown::Both);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the underlying stream
|
/// Sets the underlying stream
|
||||||
@@ -238,11 +243,12 @@ impl SmtpConnection {
|
|||||||
|
|
||||||
/// Sends the message content
|
/// Sends the message content
|
||||||
pub fn message(&mut self, message: &[u8]) -> Result<Response, Error> {
|
pub fn message(&mut self, message: &[u8]) -> Result<Response, Error> {
|
||||||
let mut out_buf: Vec<u8> = vec![];
|
|
||||||
let mut codec = ClientCodec::new();
|
let mut codec = ClientCodec::new();
|
||||||
|
let mut out_buf = Vec::with_capacity(message.len());
|
||||||
codec.encode(message, &mut out_buf);
|
codec.encode(message, &mut out_buf);
|
||||||
self.write(out_buf.as_slice())?;
|
self.write(out_buf.as_slice())?;
|
||||||
self.write(b"\r\n.\r\n")?;
|
self.write(b"\r\n.\r\n")?;
|
||||||
|
|
||||||
self.read_response()
|
self.read_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,7 +303,7 @@ impl SmtpConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The X509 certificate of the server (DER encoded)
|
/// The X509 certificate of the server (DER encoded)
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
pub fn peer_certificate(&self) -> Result<Vec<u8>, Error> {
|
pub fn peer_certificate(&self) -> Result<Vec<u8>, Error> {
|
||||||
self.stream.get_ref().peer_certificate()
|
self.stream.get_ref().peer_certificate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,10 @@ pub use self::async_connection::AsyncSmtpConnection;
|
|||||||
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
|
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
|
||||||
pub use self::async_net::AsyncNetworkStream;
|
pub use self::async_net::AsyncNetworkStream;
|
||||||
use self::net::NetworkStream;
|
use self::net::NetworkStream;
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
pub(super) use self::tls::InnerTlsParameters;
|
pub(super) use self::tls::InnerTlsParameters;
|
||||||
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
|
pub use self::tls::TlsVersion;
|
||||||
pub use self::{
|
pub use self::{
|
||||||
connection::SmtpConnection,
|
connection::SmtpConnection,
|
||||||
tls::{Certificate, Tls, TlsParameters, TlsParametersBuilder},
|
tls::{Certificate, Tls, TlsParameters, TlsParametersBuilder},
|
||||||
@@ -46,60 +48,57 @@ mod net;
|
|||||||
mod tls;
|
mod tls;
|
||||||
|
|
||||||
/// The codec used for transparency
|
/// The codec used for transparency
|
||||||
#[derive(Default, Clone, Copy, Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
struct ClientCodec {
|
struct ClientCodec {
|
||||||
escape_count: u8,
|
status: CodecStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientCodec {
|
impl ClientCodec {
|
||||||
/// Creates a new client codec
|
/// Creates a new client codec
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
ClientCodec::default()
|
Self {
|
||||||
|
status: CodecStatus::StartOfNewLine,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds transparency
|
/// Adds transparency
|
||||||
fn encode(&mut self, frame: &[u8], buf: &mut Vec<u8>) {
|
fn encode(&mut self, frame: &[u8], buf: &mut Vec<u8>) {
|
||||||
match frame.len() {
|
for &b in frame {
|
||||||
0 => {
|
buf.push(b);
|
||||||
match self.escape_count {
|
match (b, self.status) {
|
||||||
0 => buf.extend_from_slice(b"\r\n.\r\n"),
|
(b'\r', _) => {
|
||||||
1 => buf.extend_from_slice(b"\n.\r\n"),
|
self.status = CodecStatus::StartingNewLine;
|
||||||
2 => buf.extend_from_slice(b".\r\n"),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
}
|
||||||
self.escape_count = 0;
|
(b'\n', CodecStatus::StartingNewLine) => {
|
||||||
}
|
self.status = CodecStatus::StartOfNewLine;
|
||||||
_ => {
|
|
||||||
let mut start = 0;
|
|
||||||
for (idx, byte) in frame.iter().enumerate() {
|
|
||||||
match self.escape_count {
|
|
||||||
0 => self.escape_count = if *byte == b'\r' { 1 } else { 0 },
|
|
||||||
1 => self.escape_count = if *byte == b'\n' { 2 } else { 0 },
|
|
||||||
2 => {
|
|
||||||
self.escape_count = if *byte == b'.' {
|
|
||||||
3
|
|
||||||
} else if *byte == b'\r' {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
if self.escape_count == 3 {
|
|
||||||
self.escape_count = 0;
|
|
||||||
buf.extend_from_slice(&frame[start..idx]);
|
|
||||||
buf.extend_from_slice(b".");
|
|
||||||
start = idx;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
buf.extend_from_slice(&frame[start..]);
|
(_, CodecStatus::StartingNewLine) => {
|
||||||
|
self.status = CodecStatus::MiddleOfLine;
|
||||||
|
}
|
||||||
|
(b'.', CodecStatus::StartOfNewLine) => {
|
||||||
|
self.status = CodecStatus::MiddleOfLine;
|
||||||
|
buf.push(b'.');
|
||||||
|
}
|
||||||
|
(_, CodecStatus::StartOfNewLine) => {
|
||||||
|
self.status = CodecStatus::MiddleOfLine;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
|
enum CodecStatus {
|
||||||
|
/// We are past the first character of the current line
|
||||||
|
MiddleOfLine,
|
||||||
|
/// We just read a `\r` character
|
||||||
|
StartingNewLine,
|
||||||
|
/// We are at the start of a new line
|
||||||
|
StartOfNewLine,
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the string replacing all the CRLF with "\<CRLF\>"
|
/// Returns the string replacing all the CRLF with "\<CRLF\>"
|
||||||
/// Used for debug displays
|
/// Used for debug displays
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
@@ -113,9 +112,10 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_codec() {
|
fn test_codec() {
|
||||||
|
let mut buf = Vec::new();
|
||||||
let mut codec = ClientCodec::new();
|
let mut codec = ClientCodec::new();
|
||||||
let mut buf: Vec<u8> = vec![];
|
|
||||||
|
|
||||||
|
codec.encode(b".\r\n", &mut buf);
|
||||||
codec.encode(b"test\r\n", &mut buf);
|
codec.encode(b"test\r\n", &mut buf);
|
||||||
codec.encode(b"test\r\n\r\n", &mut buf);
|
codec.encode(b"test\r\n\r\n", &mut buf);
|
||||||
codec.encode(b".\r\n", &mut buf);
|
codec.encode(b".\r\n", &mut buf);
|
||||||
@@ -126,9 +126,13 @@ mod test {
|
|||||||
codec.encode(b"test\n", &mut buf);
|
codec.encode(b"test\n", &mut buf);
|
||||||
codec.encode(b".test\n", &mut buf);
|
codec.encode(b".test\n", &mut buf);
|
||||||
codec.encode(b"test", &mut buf);
|
codec.encode(b"test", &mut buf);
|
||||||
|
codec.encode(b"test", &mut buf);
|
||||||
|
codec.encode(b"test\r\n", &mut buf);
|
||||||
|
codec.encode(b".test\r\n", &mut buf);
|
||||||
|
codec.encode(b"test.\r\n", &mut buf);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
String::from_utf8(buf).unwrap(),
|
String::from_utf8(buf).unwrap(),
|
||||||
"test\r\ntest\r\n\r\n..\r\n\r\ntestte\r\n..\r\nsttesttest.test\n.test\ntest"
|
"..\r\ntest\r\ntest\r\n\r\n..\r\n\r\ntestte\r\n..\r\nsttesttest.test\n.test\ntesttesttest\r\n..test\r\ntest.\r\n"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
use boring::ssl::SslStream;
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
use native_tls::TlsStream;
|
use native_tls::TlsStream;
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
use rustls::{ClientConnection, ServerName, StreamOwned};
|
use rustls::{ClientConnection, ServerName, StreamOwned};
|
||||||
use socket2::{Domain, Protocol, Type};
|
use socket2::{Domain, Protocol, Type};
|
||||||
|
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
use super::InnerTlsParameters;
|
use super::InnerTlsParameters;
|
||||||
use super::TlsParameters;
|
use super::TlsParameters;
|
||||||
use crate::transport::smtp::{error, Error};
|
use crate::transport::smtp::{error, Error};
|
||||||
@@ -35,6 +37,8 @@ enum InnerNetworkStream {
|
|||||||
/// Encrypted TCP stream
|
/// Encrypted TCP stream
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
RustlsTls(StreamOwned<ClientConnection, TcpStream>),
|
RustlsTls(StreamOwned<ClientConnection, TcpStream>),
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
BoringTls(SslStream<TcpStream>),
|
||||||
/// Can't be built
|
/// Can't be built
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
@@ -56,6 +60,8 @@ impl NetworkStream {
|
|||||||
InnerNetworkStream::NativeTls(ref s) => s.get_ref().peer_addr(),
|
InnerNetworkStream::NativeTls(ref s) => s.get_ref().peer_addr(),
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
InnerNetworkStream::RustlsTls(ref s) => s.get_ref().peer_addr(),
|
InnerNetworkStream::RustlsTls(ref s) => s.get_ref().peer_addr(),
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
InnerNetworkStream::BoringTls(ref s) => s.get_ref().peer_addr(),
|
||||||
InnerNetworkStream::None => {
|
InnerNetworkStream::None => {
|
||||||
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
||||||
Ok(SocketAddr::V4(SocketAddrV4::new(
|
Ok(SocketAddr::V4(SocketAddrV4::new(
|
||||||
@@ -74,6 +80,8 @@ impl NetworkStream {
|
|||||||
InnerNetworkStream::NativeTls(ref s) => s.get_ref().shutdown(how),
|
InnerNetworkStream::NativeTls(ref s) => s.get_ref().shutdown(how),
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
InnerNetworkStream::RustlsTls(ref s) => s.get_ref().shutdown(how),
|
InnerNetworkStream::RustlsTls(ref s) => s.get_ref().shutdown(how),
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
InnerNetworkStream::BoringTls(ref s) => s.get_ref().shutdown(how),
|
||||||
InnerNetworkStream::None => {
|
InnerNetworkStream::None => {
|
||||||
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -137,13 +145,17 @@ impl NetworkStream {
|
|||||||
|
|
||||||
pub fn upgrade_tls(&mut self, tls_parameters: &TlsParameters) -> Result<(), Error> {
|
pub fn upgrade_tls(&mut self, tls_parameters: &TlsParameters) -> Result<(), Error> {
|
||||||
match &self.inner {
|
match &self.inner {
|
||||||
#[cfg(not(any(feature = "native-tls", feature = "rustls-tls")))]
|
#[cfg(not(any(
|
||||||
|
feature = "native-tls",
|
||||||
|
feature = "rustls-tls",
|
||||||
|
feature = "boring-tls"
|
||||||
|
)))]
|
||||||
InnerNetworkStream::Tcp(_) => {
|
InnerNetworkStream::Tcp(_) => {
|
||||||
let _ = tls_parameters;
|
let _ = tls_parameters;
|
||||||
panic!("Trying to upgrade an NetworkStream without having enabled either the native-tls or the rustls-tls feature");
|
panic!("Trying to upgrade an NetworkStream without having enabled either the native-tls or the rustls-tls feature");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
InnerNetworkStream::Tcp(_) => {
|
InnerNetworkStream::Tcp(_) => {
|
||||||
// get owned TcpStream
|
// get owned TcpStream
|
||||||
let tcp_stream = mem::replace(&mut self.inner, InnerNetworkStream::None);
|
let tcp_stream = mem::replace(&mut self.inner, InnerNetworkStream::None);
|
||||||
@@ -159,7 +171,7 @@ impl NetworkStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
fn upgrade_tls_impl(
|
fn upgrade_tls_impl(
|
||||||
tcp_stream: TcpStream,
|
tcp_stream: TcpStream,
|
||||||
tls_parameters: &TlsParameters,
|
tls_parameters: &TlsParameters,
|
||||||
@@ -181,6 +193,16 @@ impl NetworkStream {
|
|||||||
let stream = StreamOwned::new(connection, tcp_stream);
|
let stream = StreamOwned::new(connection, tcp_stream);
|
||||||
InnerNetworkStream::RustlsTls(stream)
|
InnerNetworkStream::RustlsTls(stream)
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
InnerTlsParameters::BoringTls(connector) => {
|
||||||
|
let stream = connector
|
||||||
|
.configure()
|
||||||
|
.map_err(error::connection)?
|
||||||
|
.verify_hostname(tls_parameters.accept_invalid_hostnames)
|
||||||
|
.connect(tls_parameters.domain(), tcp_stream)
|
||||||
|
.map_err(error::connection)?;
|
||||||
|
InnerNetworkStream::BoringTls(stream)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,6 +213,8 @@ impl NetworkStream {
|
|||||||
InnerNetworkStream::NativeTls(_) => true,
|
InnerNetworkStream::NativeTls(_) => true,
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
InnerNetworkStream::RustlsTls(_) => true,
|
InnerNetworkStream::RustlsTls(_) => true,
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
InnerNetworkStream::BoringTls(_) => true,
|
||||||
InnerNetworkStream::None => {
|
InnerNetworkStream::None => {
|
||||||
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
||||||
false
|
false
|
||||||
@@ -198,7 +222,7 @@ impl NetworkStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
pub fn peer_certificate(&self) -> Result<Vec<u8>, Error> {
|
pub fn peer_certificate(&self) -> Result<Vec<u8>, Error> {
|
||||||
match &self.inner {
|
match &self.inner {
|
||||||
InnerNetworkStream::Tcp(_) => Err(error::client("Connection is not encrypted")),
|
InnerNetworkStream::Tcp(_) => Err(error::client("Connection is not encrypted")),
|
||||||
@@ -218,6 +242,13 @@ impl NetworkStream {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.clone()
|
.clone()
|
||||||
.0),
|
.0),
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
InnerNetworkStream::BoringTls(stream) => Ok(stream
|
||||||
|
.ssl()
|
||||||
|
.peer_certificate()
|
||||||
|
.unwrap()
|
||||||
|
.to_der()
|
||||||
|
.map_err(error::tls)?),
|
||||||
InnerNetworkStream::None => panic!("InnerNetworkStream::None must never be built"),
|
InnerNetworkStream::None => panic!("InnerNetworkStream::None must never be built"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,6 +264,10 @@ impl NetworkStream {
|
|||||||
InnerNetworkStream::RustlsTls(ref mut stream) => {
|
InnerNetworkStream::RustlsTls(ref mut stream) => {
|
||||||
stream.get_ref().set_read_timeout(duration)
|
stream.get_ref().set_read_timeout(duration)
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
InnerNetworkStream::BoringTls(ref mut stream) => {
|
||||||
|
stream.get_ref().set_read_timeout(duration)
|
||||||
|
}
|
||||||
InnerNetworkStream::None => {
|
InnerNetworkStream::None => {
|
||||||
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -253,7 +288,10 @@ impl NetworkStream {
|
|||||||
InnerNetworkStream::RustlsTls(ref mut stream) => {
|
InnerNetworkStream::RustlsTls(ref mut stream) => {
|
||||||
stream.get_ref().set_write_timeout(duration)
|
stream.get_ref().set_write_timeout(duration)
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
InnerNetworkStream::BoringTls(ref mut stream) => {
|
||||||
|
stream.get_ref().set_write_timeout(duration)
|
||||||
|
}
|
||||||
InnerNetworkStream::None => {
|
InnerNetworkStream::None => {
|
||||||
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -270,6 +308,8 @@ impl Read for NetworkStream {
|
|||||||
InnerNetworkStream::NativeTls(ref mut s) => s.read(buf),
|
InnerNetworkStream::NativeTls(ref mut s) => s.read(buf),
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
InnerNetworkStream::RustlsTls(ref mut s) => s.read(buf),
|
InnerNetworkStream::RustlsTls(ref mut s) => s.read(buf),
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
InnerNetworkStream::BoringTls(ref mut s) => s.read(buf),
|
||||||
InnerNetworkStream::None => {
|
InnerNetworkStream::None => {
|
||||||
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
||||||
Ok(0)
|
Ok(0)
|
||||||
@@ -286,6 +326,8 @@ impl Write for NetworkStream {
|
|||||||
InnerNetworkStream::NativeTls(ref mut s) => s.write(buf),
|
InnerNetworkStream::NativeTls(ref mut s) => s.write(buf),
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
InnerNetworkStream::RustlsTls(ref mut s) => s.write(buf),
|
InnerNetworkStream::RustlsTls(ref mut s) => s.write(buf),
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
InnerNetworkStream::BoringTls(ref mut s) => s.write(buf),
|
||||||
InnerNetworkStream::None => {
|
InnerNetworkStream::None => {
|
||||||
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
||||||
Ok(0)
|
Ok(0)
|
||||||
@@ -300,6 +342,8 @@ impl Write for NetworkStream {
|
|||||||
InnerNetworkStream::NativeTls(ref mut s) => s.flush(),
|
InnerNetworkStream::NativeTls(ref mut s) => s.flush(),
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
InnerNetworkStream::RustlsTls(ref mut s) => s.flush(),
|
InnerNetworkStream::RustlsTls(ref mut s) => s.flush(),
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
InnerNetworkStream::BoringTls(ref mut s) => s.flush(),
|
||||||
InnerNetworkStream::None => {
|
InnerNetworkStream::None => {
|
||||||
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
debug_assert!(false, "InnerNetworkStream::None must never be built");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ use std::fmt::{self, Debug};
|
|||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
use std::{sync::Arc, time::SystemTime};
|
use std::{sync::Arc, time::SystemTime};
|
||||||
|
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
use boring::ssl::{SslConnector, SslVersion};
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
use native_tls::{Protocol, TlsConnector};
|
use native_tls::{Protocol, TlsConnector};
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
@@ -10,14 +12,44 @@ use rustls::{
|
|||||||
ClientConfig, Error as TlsError, OwnedTrustAnchor, RootCertStore, ServerName,
|
ClientConfig, Error as TlsError, OwnedTrustAnchor, RootCertStore, ServerName,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
use crate::transport::smtp::{error, Error};
|
use crate::transport::smtp::{error, Error};
|
||||||
|
|
||||||
/// Accepted protocols by default.
|
/// TLS protocol versions.
|
||||||
/// This removes TLS 1.0 and 1.1 compared to tls-native defaults.
|
#[derive(Debug, Copy, Clone)]
|
||||||
// This is also rustls' default behavior
|
#[non_exhaustive]
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
const DEFAULT_TLS_MIN_PROTOCOL: Protocol = Protocol::Tlsv12;
|
pub enum TlsVersion {
|
||||||
|
/// TLS 1.0
|
||||||
|
///
|
||||||
|
/// Should only be used when trying to support legacy
|
||||||
|
/// SMTP servers that haven't updated to
|
||||||
|
/// at least TLS 1.2 yet.
|
||||||
|
///
|
||||||
|
/// Supported by `native-tls` and `boring-tls`.
|
||||||
|
Tlsv10,
|
||||||
|
/// TLS 1.1
|
||||||
|
///
|
||||||
|
/// Should only be used when trying to support legacy
|
||||||
|
/// SMTP servers that haven't updated to
|
||||||
|
/// at least TLS 1.2 yet.
|
||||||
|
///
|
||||||
|
/// Supported by `native-tls` and `boring-tls`.
|
||||||
|
Tlsv11,
|
||||||
|
/// TLS 1.2
|
||||||
|
///
|
||||||
|
/// A good option for most SMTP servers.
|
||||||
|
///
|
||||||
|
/// Supported by all TLS backends.
|
||||||
|
Tlsv12,
|
||||||
|
/// TLS 1.3
|
||||||
|
///
|
||||||
|
/// The most secure option, altough not supported by all SMTP servers.
|
||||||
|
///
|
||||||
|
/// Altough it is technically supported by all TLS backends,
|
||||||
|
/// trying to set it for `native-tls` will give a runtime error.
|
||||||
|
Tlsv13,
|
||||||
|
}
|
||||||
|
|
||||||
/// How to apply TLS to a client connection
|
/// How to apply TLS to a client connection
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -26,16 +58,25 @@ pub enum Tls {
|
|||||||
/// Insecure connection only (for testing purposes)
|
/// Insecure connection only (for testing purposes)
|
||||||
None,
|
None,
|
||||||
/// Start with insecure connection and use `STARTTLS` when available
|
/// Start with insecure connection and use `STARTTLS` when available
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
|
#[cfg_attr(
|
||||||
|
docsrs,
|
||||||
|
doc(cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls")))
|
||||||
|
)]
|
||||||
Opportunistic(TlsParameters),
|
Opportunistic(TlsParameters),
|
||||||
/// Start with insecure connection and require `STARTTLS`
|
/// Start with insecure connection and require `STARTTLS`
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
|
#[cfg_attr(
|
||||||
|
docsrs,
|
||||||
|
doc(cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls")))
|
||||||
|
)]
|
||||||
Required(TlsParameters),
|
Required(TlsParameters),
|
||||||
/// Use TLS wrapped connection
|
/// Use TLS wrapped connection
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
|
#[cfg_attr(
|
||||||
|
docsrs,
|
||||||
|
doc(cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls")))
|
||||||
|
)]
|
||||||
Wrapper(TlsParameters),
|
Wrapper(TlsParameters),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,11 +84,11 @@ impl Debug for Tls {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match &self {
|
match &self {
|
||||||
Self::None => f.pad("None"),
|
Self::None => f.pad("None"),
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
Self::Opportunistic(_) => f.pad("Opportunistic"),
|
Self::Opportunistic(_) => f.pad("Opportunistic"),
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
Self::Required(_) => f.pad("Required"),
|
Self::Required(_) => f.pad("Required"),
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
Self::Wrapper(_) => f.pad("Wrapper"),
|
Self::Wrapper(_) => f.pad("Wrapper"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,6 +100,7 @@ pub struct TlsParameters {
|
|||||||
pub(crate) connector: InnerTlsParameters,
|
pub(crate) connector: InnerTlsParameters,
|
||||||
/// The domain name which is expected in the TLS certificate from the server
|
/// The domain name which is expected in the TLS certificate from the server
|
||||||
pub(super) domain: String,
|
pub(super) domain: String,
|
||||||
|
pub(super) accept_invalid_hostnames: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builder for `TlsParameters`
|
/// Builder for `TlsParameters`
|
||||||
@@ -68,6 +110,8 @@ pub struct TlsParametersBuilder {
|
|||||||
root_certs: Vec<Certificate>,
|
root_certs: Vec<Certificate>,
|
||||||
accept_invalid_hostnames: bool,
|
accept_invalid_hostnames: bool,
|
||||||
accept_invalid_certs: bool,
|
accept_invalid_certs: bool,
|
||||||
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
|
min_tls_version: TlsVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TlsParametersBuilder {
|
impl TlsParametersBuilder {
|
||||||
@@ -78,6 +122,8 @@ impl TlsParametersBuilder {
|
|||||||
root_certs: Vec::new(),
|
root_certs: Vec::new(),
|
||||||
accept_invalid_hostnames: false,
|
accept_invalid_hostnames: false,
|
||||||
accept_invalid_certs: false,
|
accept_invalid_certs: false,
|
||||||
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
|
min_tls_version: TlsVersion::Tlsv12,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,13 +148,22 @@ impl TlsParametersBuilder {
|
|||||||
/// This method introduces significant vulnerabilities to man-in-the-middle attacks.
|
/// This method introduces significant vulnerabilities to man-in-the-middle attacks.
|
||||||
///
|
///
|
||||||
/// Hostname verification can only be disabled with the `native-tls` TLS backend.
|
/// Hostname verification can only be disabled with the `native-tls` TLS backend.
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(any(feature = "native-tls", feature = "boring-tls"))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
|
#[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "boring-tls"))))]
|
||||||
pub fn dangerous_accept_invalid_hostnames(mut self, accept_invalid_hostnames: bool) -> Self {
|
pub fn dangerous_accept_invalid_hostnames(mut self, accept_invalid_hostnames: bool) -> Self {
|
||||||
self.accept_invalid_hostnames = accept_invalid_hostnames;
|
self.accept_invalid_hostnames = accept_invalid_hostnames;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Controls which minimum TLS version is allowed
|
||||||
|
///
|
||||||
|
/// Defaults to [`Tlsv12`][TlsVersion::Tlsv12].
|
||||||
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
|
pub fn set_min_tls_version(mut self, min_tls_version: TlsVersion) -> Self {
|
||||||
|
self.min_tls_version = min_tls_version;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Controls whether invalid certificates are accepted
|
/// Controls whether invalid certificates are accepted
|
||||||
///
|
///
|
||||||
/// Defaults to `false`.
|
/// Defaults to `false`.
|
||||||
@@ -130,16 +185,20 @@ impl TlsParametersBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `TlsParameters` using native-tls or rustls
|
/// Creates a new `TlsParameters` using native-tls, boring-tls or rustls
|
||||||
/// depending on which one is available
|
/// depending on which one is available
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
|
#[cfg_attr(
|
||||||
|
docsrs,
|
||||||
|
doc(cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls")))
|
||||||
|
)]
|
||||||
pub fn build(self) -> Result<TlsParameters, Error> {
|
pub fn build(self) -> Result<TlsParameters, Error> {
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
return self.build_rustls();
|
return self.build_rustls();
|
||||||
|
#[cfg(all(not(feature = "rustls-tls"), feature = "native-tls"))]
|
||||||
#[cfg(not(feature = "rustls-tls"))]
|
|
||||||
return self.build_native();
|
return self.build_native();
|
||||||
|
#[cfg(all(not(feature = "rustls-tls"), feature = "boring-tls"))]
|
||||||
|
return self.build_boring();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new `TlsParameters` using native-tls with the provided configuration
|
/// Creates a new `TlsParameters` using native-tls with the provided configuration
|
||||||
@@ -154,11 +213,59 @@ impl TlsParametersBuilder {
|
|||||||
tls_builder.danger_accept_invalid_hostnames(self.accept_invalid_hostnames);
|
tls_builder.danger_accept_invalid_hostnames(self.accept_invalid_hostnames);
|
||||||
tls_builder.danger_accept_invalid_certs(self.accept_invalid_certs);
|
tls_builder.danger_accept_invalid_certs(self.accept_invalid_certs);
|
||||||
|
|
||||||
tls_builder.min_protocol_version(Some(DEFAULT_TLS_MIN_PROTOCOL));
|
let min_tls_version = match self.min_tls_version {
|
||||||
|
TlsVersion::Tlsv10 => Protocol::Tlsv10,
|
||||||
|
TlsVersion::Tlsv11 => Protocol::Tlsv11,
|
||||||
|
TlsVersion::Tlsv12 => Protocol::Tlsv12,
|
||||||
|
TlsVersion::Tlsv13 => {
|
||||||
|
return Err(error::tls(
|
||||||
|
"min tls version Tlsv13 not supported in native tls",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tls_builder.min_protocol_version(Some(min_tls_version));
|
||||||
let connector = tls_builder.build().map_err(error::tls)?;
|
let connector = tls_builder.build().map_err(error::tls)?;
|
||||||
Ok(TlsParameters {
|
Ok(TlsParameters {
|
||||||
connector: InnerTlsParameters::NativeTls(connector),
|
connector: InnerTlsParameters::NativeTls(connector),
|
||||||
domain: self.domain,
|
domain: self.domain,
|
||||||
|
accept_invalid_hostnames: self.accept_invalid_hostnames,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `TlsParameters` using boring-tls with the provided configuration
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||||
|
pub fn build_boring(self) -> Result<TlsParameters, Error> {
|
||||||
|
use boring::ssl::{SslMethod, SslVerifyMode};
|
||||||
|
|
||||||
|
let mut tls_builder = SslConnector::builder(SslMethod::tls_client()).map_err(error::tls)?;
|
||||||
|
|
||||||
|
if self.accept_invalid_certs {
|
||||||
|
tls_builder.set_verify(SslVerifyMode::NONE);
|
||||||
|
} else {
|
||||||
|
let cert_store = tls_builder.cert_store_mut();
|
||||||
|
|
||||||
|
for cert in self.root_certs {
|
||||||
|
cert_store.add_cert(cert.boring_tls).map_err(error::tls)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let min_tls_version = match self.min_tls_version {
|
||||||
|
TlsVersion::Tlsv10 => SslVersion::TLS1,
|
||||||
|
TlsVersion::Tlsv11 => SslVersion::TLS1_1,
|
||||||
|
TlsVersion::Tlsv12 => SslVersion::TLS1_2,
|
||||||
|
TlsVersion::Tlsv13 => SslVersion::TLS1_3,
|
||||||
|
};
|
||||||
|
|
||||||
|
tls_builder
|
||||||
|
.set_min_proto_version(Some(min_tls_version))
|
||||||
|
.map_err(error::tls)?;
|
||||||
|
let connector = tls_builder.build();
|
||||||
|
Ok(TlsParameters {
|
||||||
|
connector: InnerTlsParameters::BoringTls(connector),
|
||||||
|
domain: self.domain,
|
||||||
|
accept_invalid_hostnames: self.accept_invalid_hostnames,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +274,24 @@ impl TlsParametersBuilder {
|
|||||||
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
|
||||||
pub fn build_rustls(self) -> Result<TlsParameters, Error> {
|
pub fn build_rustls(self) -> Result<TlsParameters, Error> {
|
||||||
let tls = ClientConfig::builder();
|
let tls = ClientConfig::builder();
|
||||||
let tls = tls.with_safe_defaults();
|
|
||||||
|
let just_version3 = &[&rustls::version::TLS13];
|
||||||
|
let supported_versions = match self.min_tls_version {
|
||||||
|
TlsVersion::Tlsv10 => {
|
||||||
|
return Err(error::tls("min tls version Tlsv10 not supported in rustls"))
|
||||||
|
}
|
||||||
|
TlsVersion::Tlsv11 => {
|
||||||
|
return Err(error::tls("min tls version Tlsv11 not supported in rustls"))
|
||||||
|
}
|
||||||
|
TlsVersion::Tlsv12 => rustls::ALL_VERSIONS,
|
||||||
|
TlsVersion::Tlsv13 => just_version3,
|
||||||
|
};
|
||||||
|
|
||||||
|
let tls = tls
|
||||||
|
.with_safe_default_cipher_suites()
|
||||||
|
.with_safe_default_kx_groups()
|
||||||
|
.with_protocol_versions(supported_versions)
|
||||||
|
.map_err(error::tls)?;
|
||||||
|
|
||||||
let tls = if self.accept_invalid_certs {
|
let tls = if self.accept_invalid_certs {
|
||||||
tls.with_custom_certificate_verifier(Arc::new(InvalidCertsVerifier {}))
|
tls.with_custom_certificate_verifier(Arc::new(InvalidCertsVerifier {}))
|
||||||
@@ -198,23 +322,30 @@ impl TlsParametersBuilder {
|
|||||||
Ok(TlsParameters {
|
Ok(TlsParameters {
|
||||||
connector: InnerTlsParameters::RustlsTls(Arc::new(tls)),
|
connector: InnerTlsParameters::RustlsTls(Arc::new(tls)),
|
||||||
domain: self.domain,
|
domain: self.domain,
|
||||||
|
accept_invalid_hostnames: self.accept_invalid_hostnames,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
pub enum InnerTlsParameters {
|
pub enum InnerTlsParameters {
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
NativeTls(TlsConnector),
|
NativeTls(TlsConnector),
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
RustlsTls(Arc<ClientConfig>),
|
RustlsTls(Arc<ClientConfig>),
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
BoringTls(SslConnector),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TlsParameters {
|
impl TlsParameters {
|
||||||
/// Creates a new `TlsParameters` using native-tls or rustls
|
/// Creates a new `TlsParameters` using native-tls or rustls
|
||||||
/// depending on which one is available
|
/// depending on which one is available
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
|
#[cfg_attr(
|
||||||
|
docsrs,
|
||||||
|
doc(cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls")))
|
||||||
|
)]
|
||||||
pub fn new(domain: String) -> Result<Self, Error> {
|
pub fn new(domain: String) -> Result<Self, Error> {
|
||||||
TlsParametersBuilder::new(domain).build()
|
TlsParametersBuilder::new(domain).build()
|
||||||
}
|
}
|
||||||
@@ -238,6 +369,13 @@ impl TlsParameters {
|
|||||||
TlsParametersBuilder::new(domain).build_rustls()
|
TlsParametersBuilder::new(domain).build_rustls()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a new `TlsParameters` using boring
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "boring-tls")))]
|
||||||
|
pub fn new_boring(domain: String) -> Result<Self, Error> {
|
||||||
|
TlsParametersBuilder::new(domain).build_boring()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn domain(&self) -> &str {
|
pub fn domain(&self) -> &str {
|
||||||
&self.domain
|
&self.domain
|
||||||
}
|
}
|
||||||
@@ -251,20 +389,27 @@ pub struct Certificate {
|
|||||||
native_tls: native_tls::Certificate,
|
native_tls: native_tls::Certificate,
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
rustls: Vec<rustls::Certificate>,
|
rustls: Vec<rustls::Certificate>,
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
boring_tls: boring::x509::X509,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
impl Certificate {
|
impl Certificate {
|
||||||
/// Create a `Certificate` from a DER encoded certificate
|
/// Create a `Certificate` from a DER encoded certificate
|
||||||
pub fn from_der(der: Vec<u8>) -> Result<Self, Error> {
|
pub fn from_der(der: Vec<u8>) -> Result<Self, Error> {
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
let native_tls_cert = native_tls::Certificate::from_der(&der).map_err(error::tls)?;
|
let native_tls_cert = native_tls::Certificate::from_der(&der).map_err(error::tls)?;
|
||||||
|
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
let boring_tls_cert = boring::x509::X509::from_der(&der).map_err(error::tls)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
native_tls: native_tls_cert,
|
native_tls: native_tls_cert,
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
rustls: vec![rustls::Certificate(der)],
|
rustls: vec![rustls::Certificate(der)],
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
boring_tls: boring_tls_cert,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,6 +418,9 @@ impl Certificate {
|
|||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
let native_tls_cert = native_tls::Certificate::from_pem(pem).map_err(error::tls)?;
|
let native_tls_cert = native_tls::Certificate::from_pem(pem).map_err(error::tls)?;
|
||||||
|
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
let boring_tls_cert = boring::x509::X509::from_pem(pem).map_err(error::tls)?;
|
||||||
|
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
let rustls_cert = {
|
let rustls_cert = {
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
@@ -290,6 +438,8 @@ impl Certificate {
|
|||||||
native_tls: native_tls_cert,
|
native_tls: native_tls_cert,
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
rustls: rustls_cert,
|
rustls: rustls_cert,
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
boring_tls: boring_tls_cert,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,8 +68,11 @@ impl Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the error is from TLS
|
/// Returns true if the error is from TLS
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
|
#[cfg_attr(
|
||||||
|
docsrs,
|
||||||
|
doc(cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls")))
|
||||||
|
)]
|
||||||
pub fn is_tls(&self) -> bool {
|
pub fn is_tls(&self) -> bool {
|
||||||
matches!(self.inner.kind, Kind::Tls)
|
matches!(self.inner.kind, Kind::Tls)
|
||||||
}
|
}
|
||||||
@@ -102,8 +105,11 @@ pub(crate) enum Kind {
|
|||||||
/// Underlying network i/o error
|
/// Underlying network i/o error
|
||||||
Network,
|
Network,
|
||||||
/// TLS error
|
/// TLS error
|
||||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
|
#[cfg_attr(
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
docsrs,
|
||||||
|
doc(cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls")))
|
||||||
|
)]
|
||||||
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
Tls,
|
Tls,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +134,7 @@ impl fmt::Display for Error {
|
|||||||
Kind::Client => f.write_str("internal client error")?,
|
Kind::Client => f.write_str("internal client error")?,
|
||||||
Kind::Network => f.write_str("network error")?,
|
Kind::Network => f.write_str("network error")?,
|
||||||
Kind::Connection => f.write_str("Connection error")?,
|
Kind::Connection => f.write_str("Connection error")?,
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
Kind::Tls => f.write_str("tls error")?,
|
Kind::Tls => f.write_str("tls error")?,
|
||||||
Kind::Transient(ref code) => {
|
Kind::Transient(ref code) => {
|
||||||
write!(f, "transient error ({})", code)?;
|
write!(f, "transient error ({})", code)?;
|
||||||
@@ -179,7 +185,7 @@ pub(crate) fn connection<E: Into<BoxError>>(e: E) -> Error {
|
|||||||
Error::new(Kind::Connection, Some(e))
|
Error::new(Kind::Connection, Some(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
pub(crate) fn tls<E: Into<BoxError>>(e: E) -> Error {
|
pub(crate) fn tls<E: Into<BoxError>>(e: E) -> Error {
|
||||||
Error::new(Kind::Tls, Some(e))
|
Error::new(Kind::Tls, Some(e))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ pub use self::{
|
|||||||
error::Error,
|
error::Error,
|
||||||
transport::{SmtpTransport, SmtpTransportBuilder},
|
transport::{SmtpTransport, SmtpTransportBuilder},
|
||||||
};
|
};
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
use crate::transport::smtp::client::TlsParameters;
|
use crate::transport::smtp::client::TlsParameters;
|
||||||
use crate::transport::smtp::{
|
use crate::transport::smtp::{
|
||||||
authentication::{Credentials, Mechanism, DEFAULT_MECHANISMS},
|
authentication::{Credentials, Mechanism, DEFAULT_MECHANISMS},
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use super::pool::sync_impl::Pool;
|
|||||||
#[cfg(feature = "pool")]
|
#[cfg(feature = "pool")]
|
||||||
use super::PoolConfig;
|
use super::PoolConfig;
|
||||||
use super::{ClientId, Credentials, Error, Mechanism, Response, SmtpConnection, SmtpInfo};
|
use super::{ClientId, Credentials, Error, Mechanism, Response, SmtpConnection, SmtpInfo};
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
use super::{Tls, TlsParameters, SUBMISSIONS_PORT, SUBMISSION_PORT};
|
use super::{Tls, TlsParameters, SUBMISSIONS_PORT, SUBMISSION_PORT};
|
||||||
use crate::{address::Envelope, Transport};
|
use crate::{address::Envelope, Transport};
|
||||||
|
|
||||||
@@ -45,8 +45,11 @@ impl SmtpTransport {
|
|||||||
///
|
///
|
||||||
/// Creates an encrypted transport over submissions port, using the provided domain
|
/// Creates an encrypted transport over submissions port, using the provided domain
|
||||||
/// to validate TLS certificates.
|
/// to validate TLS certificates.
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
|
#[cfg_attr(
|
||||||
|
docsrs,
|
||||||
|
doc(cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls")))
|
||||||
|
)]
|
||||||
pub fn relay(relay: &str) -> Result<SmtpTransportBuilder, Error> {
|
pub fn relay(relay: &str) -> Result<SmtpTransportBuilder, Error> {
|
||||||
let tls_parameters = TlsParameters::new(relay.into())?;
|
let tls_parameters = TlsParameters::new(relay.into())?;
|
||||||
|
|
||||||
@@ -66,8 +69,11 @@ impl SmtpTransport {
|
|||||||
///
|
///
|
||||||
/// An error is returned if the connection can't be upgraded. No credentials
|
/// An error is returned if the connection can't be upgraded. No credentials
|
||||||
/// or emails will be sent to the server, protecting from downgrade attacks.
|
/// or emails will be sent to the server, protecting from downgrade attacks.
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
|
#[cfg_attr(
|
||||||
|
docsrs,
|
||||||
|
doc(cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls")))
|
||||||
|
)]
|
||||||
pub fn starttls_relay(relay: &str) -> Result<SmtpTransportBuilder, Error> {
|
pub fn starttls_relay(relay: &str) -> Result<SmtpTransportBuilder, Error> {
|
||||||
let tls_parameters = TlsParameters::new(relay.into())?;
|
let tls_parameters = TlsParameters::new(relay.into())?;
|
||||||
|
|
||||||
@@ -166,8 +172,11 @@ impl SmtpTransportBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set the TLS settings to use
|
/// Set the TLS settings to use
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "rustls-tls"))))]
|
#[cfg_attr(
|
||||||
|
docsrs,
|
||||||
|
doc(cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls")))
|
||||||
|
)]
|
||||||
pub fn tls(mut self, tls: Tls) -> Self {
|
pub fn tls(mut self, tls: Tls) -> Self {
|
||||||
self.info.tls = tls;
|
self.info.tls = tls;
|
||||||
self
|
self
|
||||||
@@ -210,7 +219,7 @@ impl SmtpClient {
|
|||||||
pub fn connection(&self) -> Result<SmtpConnection, Error> {
|
pub fn connection(&self) -> Result<SmtpConnection, Error> {
|
||||||
#[allow(clippy::match_single_binding)]
|
#[allow(clippy::match_single_binding)]
|
||||||
let tls_parameters = match self.info.tls {
|
let tls_parameters = match self.info.tls {
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
Tls::Wrapper(ref tls_parameters) => Some(tls_parameters),
|
Tls::Wrapper(ref tls_parameters) => Some(tls_parameters),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
@@ -224,7 +233,7 @@ impl SmtpClient {
|
|||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
match self.info.tls {
|
match self.info.tls {
|
||||||
Tls::Opportunistic(ref tls_parameters) => {
|
Tls::Opportunistic(ref tls_parameters) => {
|
||||||
if conn.can_starttls() {
|
if conn.can_starttls() {
|
||||||
|
|||||||
Reference in New Issue
Block a user