Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51794aa912 | ||
|
|
eb42651401 | ||
|
|
99c6dc2a87 | ||
|
|
b6babbce00 | ||
|
|
c9895c52de | ||
|
|
575492b9ed | ||
|
|
ad665cd01e | ||
|
|
e2ac5dadfb | ||
|
|
1c6a348eb8 | ||
|
|
e8b2498ad7 | ||
|
|
bf48bd6b96 | ||
|
|
fa6191983a | ||
|
|
ca405040ae | ||
|
|
f7a1b790df | ||
|
|
caff354cbf | ||
|
|
a81401c4cb | ||
|
|
54df594d6c | ||
|
|
cada01d039 | ||
|
|
0132bee59d | ||
|
|
acdf189717 | ||
|
|
3aea65315f |
20
.github/workflows/test.yml
vendored
20
.github/workflows/test.yml
vendored
@@ -13,16 +13,16 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
rustfmt:
|
rustfmt:
|
||||||
name: rustfmt / nightly-2023-06-22
|
name: rustfmt / nightly-2024-09-01
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install rust
|
- name: Install rust
|
||||||
run: |
|
run: |
|
||||||
rustup default nightly-2023-06-22
|
rustup default nightly-2024-09-01
|
||||||
rustup component add rustfmt
|
rustup component add rustfmt
|
||||||
|
|
||||||
- name: cargo fmt
|
- name: cargo fmt
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install rust
|
- name: Install rust
|
||||||
run: |
|
run: |
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install rust
|
- name: Install rust
|
||||||
run: rustup update --no-self-update stable
|
run: rustup update --no-self-update stable
|
||||||
@@ -80,7 +80,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install rust
|
- name: Install rust
|
||||||
run: |
|
run: |
|
||||||
@@ -112,12 +112,6 @@ jobs:
|
|||||||
- name: Install dkimverify
|
- name: Install dkimverify
|
||||||
run: sudo apt -y install python3-dkim
|
run: sudo apt -y install python3-dkim
|
||||||
|
|
||||||
- name: Work around early dependencies MSRV bump
|
|
||||||
run: |
|
|
||||||
cargo update -p anstyle --precise 1.0.2
|
|
||||||
cargo update -p clap --precise 4.3.24
|
|
||||||
cargo update -p clap_lex --precise 0.5.0
|
|
||||||
|
|
||||||
- name: Test with no default features
|
- name: Test with no default features
|
||||||
run: cargo test --no-default-features
|
run: cargo test --no-default-features
|
||||||
|
|
||||||
@@ -134,7 +128,7 @@ jobs:
|
|||||||
# name: Coverage
|
# name: Coverage
|
||||||
# runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
# steps:
|
# steps:
|
||||||
# - uses: actions/checkout@v2
|
# - uses: actions/checkout@v4
|
||||||
# - uses: actions-rs/toolchain@v1
|
# - uses: actions-rs/toolchain@v1
|
||||||
# with:
|
# with:
|
||||||
# toolchain: nightly
|
# toolchain: nightly
|
||||||
|
|||||||
53
CHANGELOG.md
53
CHANGELOG.md
@@ -1,3 +1,56 @@
|
|||||||
|
<a name="v0.11.10"></a>
|
||||||
|
### v0.11.10 (2024-10-23)
|
||||||
|
|
||||||
|
#### Bug fixes
|
||||||
|
|
||||||
|
* Ignore disconnect errors when `pool` feature of SMTP transport is disabled ([#999])
|
||||||
|
* Use case insensitive comparisons for matching login challenge requests ([#1000])
|
||||||
|
|
||||||
|
[#999]: https://github.com/lettre/lettre/pull/999
|
||||||
|
[#1000]: https://github.com/lettre/lettre/pull/1000
|
||||||
|
|
||||||
|
<a name="v0.11.9"></a>
|
||||||
|
### v0.11.9 (2024-09-13)
|
||||||
|
|
||||||
|
#### Bug fixes
|
||||||
|
|
||||||
|
* Fix feature gate for `accept_invalid_hostnames` for rustls ([#988])
|
||||||
|
* Fix parsing `Mailbox` with trailing spaces ([#986])
|
||||||
|
|
||||||
|
#### Misc
|
||||||
|
|
||||||
|
* Bump `rustls-native-certs` to v0.8 ([#992])
|
||||||
|
* Make getting started example in readme complete ([#990])
|
||||||
|
|
||||||
|
[#988]: https://github.com/lettre/lettre/pull/988
|
||||||
|
[#986]: https://github.com/lettre/lettre/pull/986
|
||||||
|
[#990]: https://github.com/lettre/lettre/pull/990
|
||||||
|
[#992]: https://github.com/lettre/lettre/pull/992
|
||||||
|
|
||||||
|
<a name="v0.11.8"></a>
|
||||||
|
### v0.11.8 (2024-09-03)
|
||||||
|
|
||||||
|
#### Features
|
||||||
|
|
||||||
|
* Add mTLS support ([#974])
|
||||||
|
* Implement `accept_invalid_hostnames` for rustls ([#977])
|
||||||
|
* Provide certificate chain for peer certificates when using `rustls` or `boring-tls` ([#976])
|
||||||
|
|
||||||
|
#### Changes
|
||||||
|
|
||||||
|
* Make `HeaderName` comparisons via `PartialEq` case insensitive ([#980])
|
||||||
|
|
||||||
|
#### Misc
|
||||||
|
|
||||||
|
* Fix clippy warnings ([#979])
|
||||||
|
* Replace manual impl of `#[non_exhaustive]` for `InvalidHeaderName` ([#981])
|
||||||
|
|
||||||
|
[#974]: https://github.com/lettre/lettre/pull/974
|
||||||
|
[#976]: https://github.com/lettre/lettre/pull/976
|
||||||
|
[#977]: https://github.com/lettre/lettre/pull/977
|
||||||
|
[#980]: https://github.com/lettre/lettre/pull/980
|
||||||
|
[#981]: https://github.com/lettre/lettre/pull/981
|
||||||
|
|
||||||
<a name="v0.11.7"></a>
|
<a name="v0.11.7"></a>
|
||||||
### v0.11.7 (2024-04-23)
|
### v0.11.7 (2024-04-23)
|
||||||
|
|
||||||
|
|||||||
1282
Cargo.lock
generated
1282
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
@@ -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.11.7"
|
version = "0.11.10"
|
||||||
description = "Email client"
|
description = "Email client"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
homepage = "https://lettre.rs"
|
homepage = "https://lettre.rs"
|
||||||
@@ -20,7 +20,7 @@ maintenance = { status = "actively-developed" }
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chumsky = "0.9"
|
chumsky = "0.9"
|
||||||
idna = "0.5"
|
idna = "1"
|
||||||
tracing = { version = "0.1.16", default-features = false, features = ["std"], optional = true } # feature
|
tracing = { version = "0.1.16", default-features = false, features = ["std"], optional = true } # feature
|
||||||
|
|
||||||
# builder
|
# builder
|
||||||
@@ -47,7 +47,8 @@ percent-encoding = { version = "2.3", optional = true }
|
|||||||
native-tls = { version = "0.2.5", optional = true } # feature
|
native-tls = { version = "0.2.5", optional = true } # feature
|
||||||
rustls = { version = "0.23.5", default-features = false, features = ["ring", "logging", "std", "tls12"], optional = true }
|
rustls = { version = "0.23.5", default-features = false, features = ["ring", "logging", "std", "tls12"], optional = true }
|
||||||
rustls-pemfile = { version = "2", optional = true }
|
rustls-pemfile = { version = "2", optional = true }
|
||||||
rustls-native-certs = { version = "0.7", optional = true }
|
rustls-native-certs = { version = "0.8", optional = true }
|
||||||
|
rustls-pki-types = { version = "1.7", optional = true }
|
||||||
webpki-roots = { version = "0.26", optional = true }
|
webpki-roots = { version = "0.26", optional = true }
|
||||||
boring = { version = "4", optional = true }
|
boring = { version = "4", optional = true }
|
||||||
|
|
||||||
@@ -58,7 +59,6 @@ async-trait = { version = "0.1", optional = true }
|
|||||||
|
|
||||||
## async-std
|
## async-std
|
||||||
async-std = { version = "1.8", optional = true }
|
async-std = { version = "1.8", optional = true }
|
||||||
#async-native-tls = { version = "0.3.3", optional = true }
|
|
||||||
futures-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12", "ring"], optional = true }
|
futures-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12", "ring"], optional = true }
|
||||||
|
|
||||||
## tokio
|
## tokio
|
||||||
@@ -108,13 +108,12 @@ smtp-transport = ["dep:base64", "dep:nom", "dep:socket2", "dep:url", "dep:percen
|
|||||||
|
|
||||||
pool = ["dep:futures-util"]
|
pool = ["dep:futures-util"]
|
||||||
|
|
||||||
rustls-tls = ["dep:webpki-roots", "dep:rustls", "dep:rustls-pemfile"]
|
rustls-tls = ["dep:webpki-roots", "dep:rustls", "dep:rustls-pemfile", "dep:rustls-pki-types"]
|
||||||
|
|
||||||
boring-tls = ["dep:boring"]
|
boring-tls = ["dep:boring"]
|
||||||
|
|
||||||
# async
|
# async
|
||||||
async-std1 = ["dep:async-std", "dep:async-trait", "dep:futures-io", "dep:futures-util"]
|
async-std1 = ["dep:async-std", "dep:async-trait", "dep:futures-io", "dep:futures-util"]
|
||||||
#async-std1-native-tls = ["async-std1", "native-tls", "dep:async-native-tls"]
|
|
||||||
async-std1-rustls-tls = ["async-std1", "rustls-tls", "dep:futures-rustls"]
|
async-std1-rustls-tls = ["async-std1", "rustls-tls", "dep:futures-rustls"]
|
||||||
tokio1 = ["dep:tokio1_crate", "dep:async-trait", "dep:futures-io", "dep:futures-util"]
|
tokio1 = ["dep:tokio1_crate", "dep:async-trait", "dep:futures-io", "dep:futures-util"]
|
||||||
tokio1-native-tls = ["tokio1", "native-tls", "dep:tokio1_native_tls_crate"]
|
tokio1-native-tls = ["tokio1", "native-tls", "dep:tokio1_native_tls_crate"]
|
||||||
@@ -123,6 +122,9 @@ tokio1-boring-tls = ["tokio1", "boring-tls", "dep:tokio1_boring"]
|
|||||||
|
|
||||||
dkim = ["dep:base64", "dep:sha2", "dep:rsa", "dep:ed25519-dalek"]
|
dkim = ["dep:base64", "dep:sha2", "dep:rsa", "dep:ed25519-dalek"]
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(lettre_ignore_tls_mismatch)'] }
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
rustdoc-args = ["--cfg", "docsrs", "--cfg", "lettre_ignore_tls_mismatch"]
|
rustdoc-args = ["--cfg", "docsrs", "--cfg", "lettre_ignore_tls_mismatch"]
|
||||||
|
|||||||
42
README.md
42
README.md
@@ -28,8 +28,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://deps.rs/crate/lettre/0.11.7">
|
<a href="https://deps.rs/crate/lettre/0.11.10">
|
||||||
<img src="https://deps.rs/crate/lettre/0.11.7/status.svg"
|
<img src="https://deps.rs/crate/lettre/0.11.10/status.svg"
|
||||||
alt="dependency status" />
|
alt="dependency status" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,27 +71,29 @@ use lettre::message::header::ContentType;
|
|||||||
use lettre::transport::smtp::authentication::Credentials;
|
use lettre::transport::smtp::authentication::Credentials;
|
||||||
use lettre::{Message, SmtpTransport, Transport};
|
use lettre::{Message, SmtpTransport, Transport};
|
||||||
|
|
||||||
let email = Message::builder()
|
fn main() {
|
||||||
.from("NoBody <nobody@domain.tld>".parse().unwrap())
|
let email = Message::builder()
|
||||||
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
.from("NoBody <nobody@domain.tld>".parse().unwrap())
|
||||||
.to("Hei <hei@domain.tld>".parse().unwrap())
|
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
|
||||||
.subject("Happy new year")
|
.to("Hei <hei@domain.tld>".parse().unwrap())
|
||||||
.header(ContentType::TEXT_PLAIN)
|
.subject("Happy new year")
|
||||||
.body(String::from("Be happy!"))
|
.header(ContentType::TEXT_PLAIN)
|
||||||
.unwrap();
|
.body(String::from("Be happy!"))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let creds = Credentials::new("smtp_username".to_owned(), "smtp_password".to_owned());
|
let creds = Credentials::new("smtp_username".to_owned(), "smtp_password".to_owned());
|
||||||
|
|
||||||
// Open a remote connection to gmail
|
// Open a remote connection to gmail
|
||||||
let mailer = SmtpTransport::relay("smtp.gmail.com")
|
let mailer = SmtpTransport::relay("smtp.gmail.com")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.credentials(creds)
|
.credentials(creds)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Send the email
|
// Send the email
|
||||||
match mailer.send(&email) {
|
match mailer.send(&email) {
|
||||||
Ok(_) => println!("Email sent successfully!"),
|
Ok(_) => println!("Email sent successfully!"),
|
||||||
Err(e) => panic!("Could not send email: {e:?}"),
|
Err(e) => panic!("Could not send email: {e:?}"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,71 @@ pub struct Envelope {
|
|||||||
/// The envelope recipient's addresses
|
/// The envelope recipient's addresses
|
||||||
///
|
///
|
||||||
/// This can not be empty.
|
/// This can not be empty.
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(deserialize_with = "serde_forward_path::deserialize")
|
||||||
|
)]
|
||||||
forward_path: Vec<Address>,
|
forward_path: Vec<Address>,
|
||||||
/// The envelope sender address
|
/// The envelope sender address
|
||||||
reverse_path: Option<Address>,
|
reverse_path: Option<Address>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// just like the default implementation to deserialize `Vec<Address>` but it
|
||||||
|
/// forbids **de**serializing empty lists
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
mod serde_forward_path {
|
||||||
|
use super::Address;
|
||||||
|
/// dummy type required for serde
|
||||||
|
/// see example: https://serde.rs/deserialize-map.html
|
||||||
|
struct CustomVisitor;
|
||||||
|
impl<'de> serde::de::Visitor<'de> for CustomVisitor {
|
||||||
|
type Value = Vec<Address>;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
formatter.write_str("a non-empty list of recipient addresses")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<S>(self, mut access: S) -> Result<Self::Value, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::de::SeqAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut seq: Vec<Address> = Vec::with_capacity(access.size_hint().unwrap_or(0));
|
||||||
|
while let Some(key) = access.next_element()? {
|
||||||
|
seq.push(key);
|
||||||
|
}
|
||||||
|
if seq.is_empty() {
|
||||||
|
Err(serde::de::Error::invalid_length(seq.len(), &self))
|
||||||
|
} else {
|
||||||
|
Ok(seq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Address>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_seq(CustomVisitor {})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn deserializing_empty_recipient_list_returns_error() {
|
||||||
|
assert!(
|
||||||
|
serde_json::from_str::<crate::address::Envelope>(r#"{"forward_path": []}"#)
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn deserializing_non_empty_recipient_list_is_ok() {
|
||||||
|
serde_json::from_str::<crate::address::Envelope>(
|
||||||
|
r#"{ "forward_path": [ {"user":"foo", "domain":"example.com"} ] }"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Envelope {
|
impl Envelope {
|
||||||
/// Creates a new envelope, which may fail if `to` is empty.
|
/// Creates a new envelope, which may fail if `to` is empty.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ impl Executor for AsyncStd1Executor {
|
|||||||
) -> Result<AsyncSmtpConnection, Error> {
|
) -> Result<AsyncSmtpConnection, Error> {
|
||||||
#[allow(clippy::match_single_binding)]
|
#[allow(clippy::match_single_binding)]
|
||||||
let tls_parameters = match tls {
|
let tls_parameters = match tls {
|
||||||
#[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))]
|
#[cfg(feature = "async-std1-rustls-tls")]
|
||||||
Tls::Wrapper(tls_parameters) => Some(tls_parameters.clone()),
|
Tls::Wrapper(tls_parameters) => Some(tls_parameters.clone()),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
@@ -243,7 +243,7 @@ impl Executor for AsyncStd1Executor {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
#[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))]
|
#[cfg(feature = "async-std1-rustls-tls")]
|
||||||
match tls {
|
match tls {
|
||||||
Tls::Opportunistic(tls_parameters) => {
|
Tls::Opportunistic(tls_parameters) => {
|
||||||
if conn.can_starttls() {
|
if conn.can_starttls() {
|
||||||
|
|||||||
18
src/lib.rs
18
src/lib.rs
@@ -109,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.11.7")]
|
#![doc(html_root_url = "https://docs.rs/crate/lettre/0.11.10")]
|
||||||
#![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)]
|
||||||
@@ -174,21 +174,7 @@ mod compiletime_checks {
|
|||||||
If you'd like to use `boring-tls` make sure that the `rustls-tls` feature hasn't been enabled by mistake.
|
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.");
|
Make sure to apply the same to any of your crate dependencies that use the `lettre` crate.");
|
||||||
|
|
||||||
/*
|
#[cfg(all(feature = "async-std1", feature = "native-tls",))]
|
||||||
#[cfg(all(
|
|
||||||
feature = "async-std1",
|
|
||||||
feature = "native-tls",
|
|
||||||
not(feature = "async-std1-native-tls")
|
|
||||||
))]
|
|
||||||
compile_error!("Lettre is being built with the `async-std1` and the `native-tls` features, but the `async-std1-native-tls` feature hasn't been turned on.
|
|
||||||
If you'd like to use rustls make sure that the `native-tls` hasn't been enabled by mistake (you may need to import lettre without default features)
|
|
||||||
If you're building a library which depends on lettre import it without default features and enable just the features you need.");
|
|
||||||
*/
|
|
||||||
#[cfg(all(
|
|
||||||
feature = "async-std1",
|
|
||||||
feature = "native-tls",
|
|
||||||
not(feature = "async-std1-native-tls")
|
|
||||||
))]
|
|
||||||
compile_error!("Lettre is being built with the `async-std1` and the `native-tls` features, but the async-std integration doesn't support native-tls yet.
|
compile_error!("Lettre is being built with the `async-std1` and the `native-tls` features, but the async-std integration doesn't support native-tls yet.
|
||||||
If you'd like to work on the issue please take a look at https://github.com/lettre/lettre/issues/576.
|
If you'd like to work on the issue please take a look at https://github.com/lettre/lettre/issues/576.
|
||||||
If you were trying to opt into `rustls-tls` and did not activate `native-tls`, disable the default-features of lettre in `Cargo.toml` and manually add the required features.
|
If you were trying to opt into `rustls-tls` and did not activate `native-tls`, disable the default-features of lettre in `Cargo.toml` and manually add the required features.
|
||||||
|
|||||||
@@ -124,22 +124,18 @@ impl Headers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn find_header(&self, name: &str) -> Option<&HeaderValue> {
|
pub(crate) fn find_header(&self, name: &str) -> Option<&HeaderValue> {
|
||||||
self.headers
|
self.headers.iter().find(|value| name == value.name)
|
||||||
.iter()
|
|
||||||
.find(|value| name.eq_ignore_ascii_case(&value.name))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_header_mut(&mut self, name: &str) -> Option<&mut HeaderValue> {
|
fn find_header_mut(&mut self, name: &str) -> Option<&mut HeaderValue> {
|
||||||
self.headers
|
self.headers.iter_mut().find(|value| name == value.name)
|
||||||
.iter_mut()
|
|
||||||
.find(|value| name.eq_ignore_ascii_case(&value.name))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_header_index(&self, name: &str) -> Option<usize> {
|
fn find_header_index(&self, name: &str) -> Option<usize> {
|
||||||
self.headers
|
self.headers
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find(|(_i, value)| name.eq_ignore_ascii_case(&value.name))
|
.find(|(_i, value)| name == value.name)
|
||||||
.map(|(i, _)| i)
|
.map(|(i, _)| i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,18 +157,9 @@ impl Display for Headers {
|
|||||||
/// A possible error when converting a `HeaderName` from another type.
|
/// A possible error when converting a `HeaderName` from another type.
|
||||||
// comes from `http` crate
|
// comes from `http` crate
|
||||||
#[allow(missing_copy_implementations)]
|
#[allow(missing_copy_implementations)]
|
||||||
#[derive(Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct InvalidHeaderName {
|
#[non_exhaustive]
|
||||||
_priv: (),
|
pub struct InvalidHeaderName;
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for InvalidHeaderName {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("InvalidHeaderName")
|
|
||||||
// skip _priv noise
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for InvalidHeaderName {
|
impl fmt::Display for InvalidHeaderName {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
@@ -189,14 +176,11 @@ pub struct HeaderName(Cow<'static, str>);
|
|||||||
impl HeaderName {
|
impl HeaderName {
|
||||||
/// Creates a new header name
|
/// Creates a new header name
|
||||||
pub fn new_from_ascii(ascii: String) -> Result<Self, InvalidHeaderName> {
|
pub fn new_from_ascii(ascii: String) -> Result<Self, InvalidHeaderName> {
|
||||||
if !ascii.is_empty()
|
if !ascii.is_empty() && ascii.len() <= 76 && ascii.is_ascii() && !ascii.contains([':', ' '])
|
||||||
&& ascii.len() <= 76
|
|
||||||
&& ascii.is_ascii()
|
|
||||||
&& !ascii.contains(|c| c == ':' || c == ' ')
|
|
||||||
{
|
{
|
||||||
Ok(Self(Cow::Owned(ascii)))
|
Ok(Self(Cow::Owned(ascii)))
|
||||||
} else {
|
} else {
|
||||||
Err(InvalidHeaderName { _priv: () })
|
Err(InvalidHeaderName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,23 +241,19 @@ impl AsRef<str> for HeaderName {
|
|||||||
|
|
||||||
impl PartialEq<HeaderName> for HeaderName {
|
impl PartialEq<HeaderName> for HeaderName {
|
||||||
fn eq(&self, other: &HeaderName) -> bool {
|
fn eq(&self, other: &HeaderName) -> bool {
|
||||||
let s1: &str = self.as_ref();
|
self.eq_ignore_ascii_case(other)
|
||||||
let s2: &str = other.as_ref();
|
|
||||||
s1 == s2
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<&str> for HeaderName {
|
impl PartialEq<&str> for HeaderName {
|
||||||
fn eq(&self, other: &&str) -> bool {
|
fn eq(&self, other: &&str) -> bool {
|
||||||
let s: &str = self.as_ref();
|
self.eq_ignore_ascii_case(other)
|
||||||
s == *other
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<HeaderName> for &str {
|
impl PartialEq<HeaderName> for &str {
|
||||||
fn eq(&self, other: &HeaderName) -> bool {
|
fn eq(&self, other: &HeaderName) -> bool {
|
||||||
let s: &str = other.as_ref();
|
self.eq_ignore_ascii_case(other)
|
||||||
*self == s
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,6 +447,60 @@ mod tests {
|
|||||||
let _ = HeaderName::new_from_ascii_str("");
|
let _ = HeaderName::new_from_ascii_str("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn headername_headername_eq() {
|
||||||
|
assert_eq!(
|
||||||
|
HeaderName::new_from_ascii_str("From"),
|
||||||
|
HeaderName::new_from_ascii_str("From")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn headername_str_eq() {
|
||||||
|
assert_eq!(HeaderName::new_from_ascii_str("From"), "From");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn str_headername_eq() {
|
||||||
|
assert_eq!("From", HeaderName::new_from_ascii_str("From"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn headername_headername_eq_case_insensitive() {
|
||||||
|
assert_eq!(
|
||||||
|
HeaderName::new_from_ascii_str("From"),
|
||||||
|
HeaderName::new_from_ascii_str("from")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn headername_str_eq_case_insensitive() {
|
||||||
|
assert_eq!(HeaderName::new_from_ascii_str("From"), "from");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn str_headername_eq_case_insensitive() {
|
||||||
|
assert_eq!("from", HeaderName::new_from_ascii_str("From"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn headername_headername_ne() {
|
||||||
|
assert_ne!(
|
||||||
|
HeaderName::new_from_ascii_str("From"),
|
||||||
|
HeaderName::new_from_ascii_str("To")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn headername_str_ne() {
|
||||||
|
assert_ne!(HeaderName::new_from_ascii_str("From"), "To");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn str_headername_ne() {
|
||||||
|
assert_ne!("From", HeaderName::new_from_ascii_str("To"));
|
||||||
|
}
|
||||||
|
|
||||||
// names taken randomly from https://it.wikipedia.org/wiki/Pinco_Pallino
|
// names taken randomly from https://it.wikipedia.org/wiki/Pinco_Pallino
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -170,7 +170,9 @@ fn phrase() -> impl Parser<char, Vec<char>, Error = Cheap<char>> {
|
|||||||
// mailbox = name-addr / addr-spec
|
// mailbox = name-addr / addr-spec
|
||||||
pub(crate) fn mailbox() -> impl Parser<char, (Option<String>, (String, String)), Error = Cheap<char>>
|
pub(crate) fn mailbox() -> impl Parser<char, (Option<String>, (String, String)), Error = Cheap<char>>
|
||||||
{
|
{
|
||||||
choice((name_addr(), addr_spec().map(|addr| (None, addr)))).then_ignore(end())
|
choice((name_addr(), addr_spec().map(|addr| (None, addr))))
|
||||||
|
.padded()
|
||||||
|
.then_ignore(end())
|
||||||
}
|
}
|
||||||
|
|
||||||
// name-addr = [display-name] angle-addr
|
// name-addr = [display-name] angle-addr
|
||||||
|
|||||||
@@ -556,6 +556,14 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_address_only_trim() {
|
||||||
|
assert_eq!(
|
||||||
|
" kayo@example.com ".parse(),
|
||||||
|
Ok(Mailbox::new(None, "kayo@example.com".parse().unwrap()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_address_with_name() {
|
fn parse_address_with_name() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -567,6 +575,17 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_address_with_name_trim() {
|
||||||
|
assert_eq!(
|
||||||
|
" K. <kayo@example.com> ".parse(),
|
||||||
|
Ok(Mailbox::new(
|
||||||
|
Some("K.".into()),
|
||||||
|
"kayo@example.com".parse().unwrap()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_address_with_empty_name() {
|
fn parse_address_with_empty_name() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -578,7 +597,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn parse_address_with_empty_name_trim() {
|
fn parse_address_with_empty_name_trim() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
" <kayo@example.com>".parse(),
|
" <kayo@example.com> ".parse(),
|
||||||
Ok(Mailbox::new(None, "kayo@example.com".parse().unwrap()))
|
Ok(Mailbox::new(None, "kayo@example.com".parse().unwrap()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
//!
|
//!
|
||||||
//! * a service from your Cloud or hosting provider
|
//! * a service from your Cloud or hosting provider
|
||||||
//! * an email server ([MTA] for Mail Transfer Agent, like Postfix or Exchange), running either
|
//! * an email server ([MTA] for Mail Transfer Agent, like Postfix or Exchange), running either
|
||||||
//! locally on your servers or accessible over the network
|
//! locally on your servers or accessible over the network
|
||||||
//! * a dedicated external service, like Mailchimp, Mailgun, etc.
|
//! * a dedicated external service, like Mailchimp, Mailgun, etc.
|
||||||
//!
|
//!
|
||||||
//! In most cases, the best option is to:
|
//! In most cases, the best option is to:
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ impl AsyncTransport for AsyncSmtpTransport<Tokio1Executor> {
|
|||||||
let result = conn.send(envelope, email).await?;
|
let result = conn.send(envelope, email).await?;
|
||||||
|
|
||||||
#[cfg(not(feature = "pool"))]
|
#[cfg(not(feature = "pool"))]
|
||||||
conn.quit().await?;
|
conn.abort().await;
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,6 @@ where
|
|||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
feature = "tokio1-native-tls",
|
feature = "tokio1-native-tls",
|
||||||
feature = "tokio1-rustls-tls",
|
feature = "tokio1-rustls-tls",
|
||||||
feature = "async-std1-native-tls",
|
|
||||||
feature = "async-std1-rustls-tls"
|
feature = "async-std1-rustls-tls"
|
||||||
))]
|
))]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -117,7 +116,6 @@ where
|
|||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
feature = "tokio1-native-tls",
|
feature = "tokio1-native-tls",
|
||||||
feature = "tokio1-rustls-tls",
|
feature = "tokio1-rustls-tls",
|
||||||
feature = "async-std1-native-tls",
|
|
||||||
feature = "async-std1-rustls-tls"
|
feature = "async-std1-rustls-tls"
|
||||||
))]
|
))]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -353,7 +351,6 @@ impl AsyncSmtpTransportBuilder {
|
|||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
feature = "tokio1-native-tls",
|
feature = "tokio1-native-tls",
|
||||||
feature = "tokio1-rustls-tls",
|
feature = "tokio1-rustls-tls",
|
||||||
feature = "async-std1-native-tls",
|
|
||||||
feature = "async-std1-rustls-tls"
|
feature = "async-std1-rustls-tls"
|
||||||
))]
|
))]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
|
|||||||
@@ -98,13 +98,17 @@ impl Mechanism {
|
|||||||
let decoded_challenge = challenge
|
let decoded_challenge = challenge
|
||||||
.ok_or_else(|| error::client("This mechanism does expect a challenge"))?;
|
.ok_or_else(|| error::client("This mechanism does expect a challenge"))?;
|
||||||
|
|
||||||
if ["User Name", "Username:", "Username", "User Name\0"]
|
if contains_ignore_ascii_case(
|
||||||
.contains(&decoded_challenge)
|
decoded_challenge,
|
||||||
{
|
["User Name", "Username:", "Username", "User Name\0"],
|
||||||
|
) {
|
||||||
return Ok(credentials.authentication_identity.clone());
|
return Ok(credentials.authentication_identity.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if ["Password", "Password:", "Password\0"].contains(&decoded_challenge) {
|
if contains_ignore_ascii_case(
|
||||||
|
decoded_challenge,
|
||||||
|
["Password", "Password:", "Password\0"],
|
||||||
|
) {
|
||||||
return Ok(credentials.secret.clone());
|
return Ok(credentials.secret.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +125,15 @@ impl Mechanism {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn contains_ignore_ascii_case<'a>(
|
||||||
|
haystack: &str,
|
||||||
|
needles: impl IntoIterator<Item = &'a str>,
|
||||||
|
) -> bool {
|
||||||
|
needles
|
||||||
|
.into_iter()
|
||||||
|
.any(|item| item.eq_ignore_ascii_case(haystack))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::{Credentials, Mechanism};
|
use super::{Credentials, Mechanism};
|
||||||
@@ -155,6 +168,23 @@ mod test {
|
|||||||
assert!(mechanism.response(&credentials, None).is_err());
|
assert!(mechanism.response(&credentials, None).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_login_case_insensitive() {
|
||||||
|
let mechanism = Mechanism::Login;
|
||||||
|
|
||||||
|
let credentials = Credentials::new("alice".to_owned(), "wonderland".to_owned());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
mechanism.response(&credentials, Some("username")).unwrap(),
|
||||||
|
"alice"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
mechanism.response(&credentials, Some("password")).unwrap(),
|
||||||
|
"wonderland"
|
||||||
|
);
|
||||||
|
assert!(mechanism.response(&credentials, None).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_xoauth2() {
|
fn test_xoauth2() {
|
||||||
let mechanism = Mechanism::Xoauth2;
|
let mechanism = Mechanism::Xoauth2;
|
||||||
|
|||||||
@@ -373,4 +373,10 @@ impl AsyncSmtpConnection {
|
|||||||
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// All the X509 certificates of the chain (DER encoded)
|
||||||
|
#[cfg(any(feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
|
pub fn certificate_chain(&self) -> Result<Vec<Vec<u8>>, Error> {
|
||||||
|
self.stream.get_ref().certificate_chain()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "async-std1-native-tls")]
|
|
||||||
use async_native_tls::TlsStream as AsyncStd1TlsStream;
|
|
||||||
#[cfg(feature = "async-std1")]
|
#[cfg(feature = "async-std1")]
|
||||||
use async_std::net::{TcpStream as AsyncStd1TcpStream, ToSocketAddrs as AsyncStd1ToSocketAddrs};
|
use async_std::net::{TcpStream as AsyncStd1TcpStream, ToSocketAddrs as AsyncStd1ToSocketAddrs};
|
||||||
use futures_io::{
|
use futures_io::{
|
||||||
@@ -36,7 +34,6 @@ use tokio1_rustls::client::TlsStream as Tokio1RustlsTlsStream;
|
|||||||
feature = "tokio1-native-tls",
|
feature = "tokio1-native-tls",
|
||||||
feature = "tokio1-rustls-tls",
|
feature = "tokio1-rustls-tls",
|
||||||
feature = "tokio1-boring-tls",
|
feature = "tokio1-boring-tls",
|
||||||
feature = "async-std1-native-tls",
|
|
||||||
feature = "async-std1-rustls-tls"
|
feature = "async-std1-rustls-tls"
|
||||||
))]
|
))]
|
||||||
use super::InnerTlsParameters;
|
use super::InnerTlsParameters;
|
||||||
@@ -86,9 +83,6 @@ enum InnerAsyncNetworkStream {
|
|||||||
#[cfg(feature = "async-std1")]
|
#[cfg(feature = "async-std1")]
|
||||||
AsyncStd1Tcp(AsyncStd1TcpStream),
|
AsyncStd1Tcp(AsyncStd1TcpStream),
|
||||||
/// Encrypted Tokio 1.x TCP stream
|
/// Encrypted Tokio 1.x TCP stream
|
||||||
#[cfg(feature = "async-std1-native-tls")]
|
|
||||||
AsyncStd1NativeTls(AsyncStd1TlsStream<AsyncStd1TcpStream>),
|
|
||||||
/// Encrypted Tokio 1.x TCP stream
|
|
||||||
#[cfg(feature = "async-std1-rustls-tls")]
|
#[cfg(feature = "async-std1-rustls-tls")]
|
||||||
AsyncStd1RustlsTls(AsyncStd1RustlsTlsStream<AsyncStd1TcpStream>),
|
AsyncStd1RustlsTls(AsyncStd1RustlsTlsStream<AsyncStd1TcpStream>),
|
||||||
/// Can't be built
|
/// Can't be built
|
||||||
@@ -119,8 +113,6 @@ impl AsyncNetworkStream {
|
|||||||
InnerAsyncNetworkStream::Tokio1BoringTls(s) => s.get_ref().peer_addr(),
|
InnerAsyncNetworkStream::Tokio1BoringTls(s) => s.get_ref().peer_addr(),
|
||||||
#[cfg(feature = "async-std1")]
|
#[cfg(feature = "async-std1")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => s.peer_addr(),
|
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => s.peer_addr(),
|
||||||
#[cfg(feature = "async-std1-native-tls")]
|
|
||||||
InnerAsyncNetworkStream::AsyncStd1NativeTls(s) => s.get_ref().peer_addr(),
|
|
||||||
#[cfg(feature = "async-std1-rustls-tls")]
|
#[cfg(feature = "async-std1-rustls-tls")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => s.get_ref().0.peer_addr(),
|
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => s.get_ref().0.peer_addr(),
|
||||||
InnerAsyncNetworkStream::None => {
|
InnerAsyncNetworkStream::None => {
|
||||||
@@ -288,16 +280,13 @@ impl AsyncNetworkStream {
|
|||||||
.map_err(error::connection)?;
|
.map_err(error::connection)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[cfg(all(
|
#[cfg(all(feature = "async-std1", not(feature = "async-std1-rustls-tls")))]
|
||||||
feature = "async-std1",
|
|
||||||
not(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))
|
|
||||||
))]
|
|
||||||
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => {
|
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => {
|
||||||
let _ = tls_parameters;
|
let _ = tls_parameters;
|
||||||
panic!("Trying to upgrade an AsyncNetworkStream without having enabled either the async-std1-native-tls or the async-std1-rustls-tls feature");
|
panic!("Trying to upgrade an AsyncNetworkStream without having enabled the async-std1-rustls-tls feature");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "async-std1-native-tls", feature = "async-std1-rustls-tls"))]
|
#[cfg(feature = "async-std1-rustls-tls")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => {
|
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => {
|
||||||
// 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);
|
||||||
@@ -385,11 +374,7 @@ impl AsyncNetworkStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
#[cfg(any(
|
#[cfg(feature = "async-std1-rustls-tls")]
|
||||||
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,
|
||||||
@@ -400,22 +385,6 @@ impl AsyncNetworkStream {
|
|||||||
#[cfg(feature = "native-tls")]
|
#[cfg(feature = "native-tls")]
|
||||||
InnerTlsParameters::NativeTls(connector) => {
|
InnerTlsParameters::NativeTls(connector) => {
|
||||||
panic!("native-tls isn't supported with async-std yet. See https://github.com/lettre/lettre/pull/531#issuecomment-757893531");
|
panic!("native-tls isn't supported with async-std yet. See https://github.com/lettre/lettre/pull/531#issuecomment-757893531");
|
||||||
|
|
||||||
/*
|
|
||||||
#[cfg(not(feature = "async-std1-native-tls"))]
|
|
||||||
panic!("built without the async-std1-native-tls feature");
|
|
||||||
|
|
||||||
#[cfg(feature = "async-std1-native-tls")]
|
|
||||||
return {
|
|
||||||
use async_native_tls::TlsConnector;
|
|
||||||
|
|
||||||
// TODO: fix
|
|
||||||
let connector: TlsConnector = todo!();
|
|
||||||
// let connector = TlsConnector::from(connector);
|
|
||||||
let stream = connector.connect(&domain, tcp_stream).await?;
|
|
||||||
Ok(InnerAsyncNetworkStream::AsyncStd1NativeTls(stream))
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
InnerTlsParameters::RustlsTls(config) => {
|
InnerTlsParameters::RustlsTls(config) => {
|
||||||
@@ -456,14 +425,54 @@ impl AsyncNetworkStream {
|
|||||||
InnerAsyncNetworkStream::Tokio1BoringTls(_) => true,
|
InnerAsyncNetworkStream::Tokio1BoringTls(_) => true,
|
||||||
#[cfg(feature = "async-std1")]
|
#[cfg(feature = "async-std1")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => false,
|
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => false,
|
||||||
#[cfg(feature = "async-std1-native-tls")]
|
|
||||||
InnerAsyncNetworkStream::AsyncStd1NativeTls(_) => true,
|
|
||||||
#[cfg(feature = "async-std1-rustls-tls")]
|
#[cfg(feature = "async-std1-rustls-tls")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(_) => true,
|
InnerAsyncNetworkStream::AsyncStd1RustlsTls(_) => true,
|
||||||
InnerAsyncNetworkStream::None => false,
|
InnerAsyncNetworkStream::None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn certificate_chain(&self) -> Result<Vec<Vec<u8>>, Error> {
|
||||||
|
match &self.inner {
|
||||||
|
#[cfg(feature = "tokio1")]
|
||||||
|
InnerAsyncNetworkStream::Tokio1Tcp(_) => {
|
||||||
|
Err(error::client("Connection is not encrypted"))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "tokio1-native-tls")]
|
||||||
|
InnerAsyncNetworkStream::Tokio1NativeTls(_) => panic!("Unsupported"),
|
||||||
|
#[cfg(feature = "tokio1-rustls-tls")]
|
||||||
|
InnerAsyncNetworkStream::Tokio1RustlsTls(stream) => Ok(stream
|
||||||
|
.get_ref()
|
||||||
|
.1
|
||||||
|
.peer_certificates()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.to_vec())
|
||||||
|
.collect()),
|
||||||
|
#[cfg(feature = "tokio1-boring-tls")]
|
||||||
|
InnerAsyncNetworkStream::Tokio1BoringTls(stream) => Ok(stream
|
||||||
|
.ssl()
|
||||||
|
.peer_cert_chain()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.to_der().map_err(error::tls))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?),
|
||||||
|
#[cfg(feature = "async-std1")]
|
||||||
|
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => {
|
||||||
|
Err(error::client("Connection is not encrypted"))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "async-std1-rustls-tls")]
|
||||||
|
InnerAsyncNetworkStream::AsyncStd1RustlsTls(stream) => Ok(stream
|
||||||
|
.get_ref()
|
||||||
|
.1
|
||||||
|
.peer_certificates()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.to_vec())
|
||||||
|
.collect()),
|
||||||
|
InnerAsyncNetworkStream::None => panic!("InnerNetworkStream::None must never be built"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn peer_certificate(&self) -> Result<Vec<u8>, Error> {
|
pub fn peer_certificate(&self) -> Result<Vec<u8>, Error> {
|
||||||
match &self.inner {
|
match &self.inner {
|
||||||
#[cfg(feature = "tokio1")]
|
#[cfg(feature = "tokio1")]
|
||||||
@@ -498,8 +507,6 @@ impl AsyncNetworkStream {
|
|||||||
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => {
|
InnerAsyncNetworkStream::AsyncStd1Tcp(_) => {
|
||||||
Err(error::client("Connection is not encrypted"))
|
Err(error::client("Connection is not encrypted"))
|
||||||
}
|
}
|
||||||
#[cfg(feature = "async-std1-native-tls")]
|
|
||||||
InnerAsyncNetworkStream::AsyncStd1NativeTls(t) => panic!("Unsupported"),
|
|
||||||
#[cfg(feature = "async-std1-rustls-tls")]
|
#[cfg(feature = "async-std1-rustls-tls")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(stream) => Ok(stream
|
InnerAsyncNetworkStream::AsyncStd1RustlsTls(stream) => Ok(stream
|
||||||
.get_ref()
|
.get_ref()
|
||||||
@@ -559,8 +566,6 @@ impl FuturesAsyncRead for AsyncNetworkStream {
|
|||||||
}
|
}
|
||||||
#[cfg(feature = "async-std1")]
|
#[cfg(feature = "async-std1")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_read(cx, buf),
|
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_read(cx, buf),
|
||||||
#[cfg(feature = "async-std1-native-tls")]
|
|
||||||
InnerAsyncNetworkStream::AsyncStd1NativeTls(s) => Pin::new(s).poll_read(cx, buf),
|
|
||||||
#[cfg(feature = "async-std1-rustls-tls")]
|
#[cfg(feature = "async-std1-rustls-tls")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_read(cx, buf),
|
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_read(cx, buf),
|
||||||
InnerAsyncNetworkStream::None => {
|
InnerAsyncNetworkStream::None => {
|
||||||
@@ -588,8 +593,6 @@ impl FuturesAsyncWrite for AsyncNetworkStream {
|
|||||||
InnerAsyncNetworkStream::Tokio1BoringTls(s) => Pin::new(s).poll_write(cx, buf),
|
InnerAsyncNetworkStream::Tokio1BoringTls(s) => Pin::new(s).poll_write(cx, buf),
|
||||||
#[cfg(feature = "async-std1")]
|
#[cfg(feature = "async-std1")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_write(cx, buf),
|
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_write(cx, buf),
|
||||||
#[cfg(feature = "async-std1-native-tls")]
|
|
||||||
InnerAsyncNetworkStream::AsyncStd1NativeTls(s) => Pin::new(s).poll_write(cx, buf),
|
|
||||||
#[cfg(feature = "async-std1-rustls-tls")]
|
#[cfg(feature = "async-std1-rustls-tls")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_write(cx, buf),
|
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_write(cx, buf),
|
||||||
InnerAsyncNetworkStream::None => {
|
InnerAsyncNetworkStream::None => {
|
||||||
@@ -611,8 +614,6 @@ impl FuturesAsyncWrite for AsyncNetworkStream {
|
|||||||
InnerAsyncNetworkStream::Tokio1BoringTls(s) => Pin::new(s).poll_flush(cx),
|
InnerAsyncNetworkStream::Tokio1BoringTls(s) => Pin::new(s).poll_flush(cx),
|
||||||
#[cfg(feature = "async-std1")]
|
#[cfg(feature = "async-std1")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_flush(cx),
|
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_flush(cx),
|
||||||
#[cfg(feature = "async-std1-native-tls")]
|
|
||||||
InnerAsyncNetworkStream::AsyncStd1NativeTls(s) => Pin::new(s).poll_flush(cx),
|
|
||||||
#[cfg(feature = "async-std1-rustls-tls")]
|
#[cfg(feature = "async-std1-rustls-tls")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_flush(cx),
|
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_flush(cx),
|
||||||
InnerAsyncNetworkStream::None => {
|
InnerAsyncNetworkStream::None => {
|
||||||
@@ -634,8 +635,6 @@ impl FuturesAsyncWrite for AsyncNetworkStream {
|
|||||||
InnerAsyncNetworkStream::Tokio1BoringTls(s) => Pin::new(s).poll_shutdown(cx),
|
InnerAsyncNetworkStream::Tokio1BoringTls(s) => Pin::new(s).poll_shutdown(cx),
|
||||||
#[cfg(feature = "async-std1")]
|
#[cfg(feature = "async-std1")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_close(cx),
|
InnerAsyncNetworkStream::AsyncStd1Tcp(s) => Pin::new(s).poll_close(cx),
|
||||||
#[cfg(feature = "async-std1-native-tls")]
|
|
||||||
InnerAsyncNetworkStream::AsyncStd1NativeTls(s) => Pin::new(s).poll_close(cx),
|
|
||||||
#[cfg(feature = "async-std1-rustls-tls")]
|
#[cfg(feature = "async-std1-rustls-tls")]
|
||||||
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_close(cx),
|
InnerAsyncNetworkStream::AsyncStd1RustlsTls(s) => Pin::new(s).poll_close(cx),
|
||||||
InnerAsyncNetworkStream::None => {
|
InnerAsyncNetworkStream::None => {
|
||||||
|
|||||||
@@ -307,4 +307,10 @@ impl SmtpConnection {
|
|||||||
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// All the X509 certificates of the chain (DER encoded)
|
||||||
|
#[cfg(any(feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
|
pub fn certificate_chain(&self) -> Result<Vec<Vec<u8>>, Error> {
|
||||||
|
self.stream.get_ref().certificate_chain()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ pub(super) use self::tls::InnerTlsParameters;
|
|||||||
pub use self::tls::TlsVersion;
|
pub use self::tls::TlsVersion;
|
||||||
pub use self::{
|
pub use self::{
|
||||||
connection::SmtpConnection,
|
connection::SmtpConnection,
|
||||||
tls::{Certificate, CertificateStore, Tls, TlsParameters, TlsParametersBuilder},
|
tls::{Certificate, CertificateStore, Identity, Tls, TlsParameters, TlsParametersBuilder},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
|
#[cfg(any(feature = "tokio1", feature = "async-std1"))]
|
||||||
@@ -139,7 +139,7 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "log")]
|
#[cfg(feature = "tracing")]
|
||||||
fn test_escape_crlf() {
|
fn test_escape_crlf() {
|
||||||
assert_eq!(escape_crlf("\r\n"), "<CRLF>");
|
assert_eq!(escape_crlf("\r\n"), "<CRLF>");
|
||||||
assert_eq!(escape_crlf("EHLO my_name\r\n"), "EHLO my_name<CRLF>");
|
assert_eq!(escape_crlf("EHLO my_name\r\n"), "EHLO my_name<CRLF>");
|
||||||
|
|||||||
@@ -223,6 +223,32 @@ impl NetworkStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
|
pub fn certificate_chain(&self) -> Result<Vec<Vec<u8>>, Error> {
|
||||||
|
match &self.inner {
|
||||||
|
InnerNetworkStream::Tcp(_) => Err(error::client("Connection is not encrypted")),
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
InnerNetworkStream::NativeTls(_) => panic!("Unsupported"),
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
InnerNetworkStream::RustlsTls(stream) => Ok(stream
|
||||||
|
.conn
|
||||||
|
.peer_certificates()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.to_vec())
|
||||||
|
.collect()),
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
InnerNetworkStream::BoringTls(stream) => Ok(stream
|
||||||
|
.ssl()
|
||||||
|
.peer_cert_chain()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.to_der().map_err(error::tls))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?),
|
||||||
|
InnerNetworkStream::None => panic!("InnerNetworkStream::None must never be built"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-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 {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::{io, sync::Arc};
|
|||||||
|
|
||||||
#[cfg(feature = "boring-tls")]
|
#[cfg(feature = "boring-tls")]
|
||||||
use boring::{
|
use boring::{
|
||||||
|
pkey::PKey,
|
||||||
ssl::{SslConnector, SslVersion},
|
ssl::{SslConnector, SslVersion},
|
||||||
x509::store::X509StoreBuilder,
|
x509::store::X509StoreBuilder,
|
||||||
};
|
};
|
||||||
@@ -12,8 +13,10 @@ use native_tls::{Protocol, TlsConnector};
|
|||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
use rustls::{
|
use rustls::{
|
||||||
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
||||||
|
crypto::WebPkiSupportedAlgorithms,
|
||||||
crypto::{verify_tls12_signature, verify_tls13_signature},
|
crypto::{verify_tls12_signature, verify_tls13_signature},
|
||||||
pki_types::{CertificateDer, ServerName, UnixTime},
|
pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime},
|
||||||
|
server::ParsedCertificate,
|
||||||
ClientConfig, DigitallySignedStruct, Error as TlsError, RootCertStore, SignatureScheme,
|
ClientConfig, DigitallySignedStruct, Error as TlsError, RootCertStore, SignatureScheme,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -108,7 +111,7 @@ pub enum CertificateStore {
|
|||||||
/// For native-tls, this will use the system certificate store on Windows, the keychain on
|
/// For native-tls, this will use the system certificate store on Windows, the keychain on
|
||||||
/// macOS, and OpenSSL directories on Linux (usually `/etc/ssl`).
|
/// macOS, and OpenSSL directories on Linux (usually `/etc/ssl`).
|
||||||
///
|
///
|
||||||
/// For rustls, this will also use the the system store if the `rustls-native-certs` feature is
|
/// For rustls, this will also use the system store if the `rustls-native-certs` feature is
|
||||||
/// enabled, or will fall back to `webpki-roots`.
|
/// enabled, or will fall back to `webpki-roots`.
|
||||||
///
|
///
|
||||||
/// The boring-tls backend uses the same logic as OpenSSL on all platforms.
|
/// The boring-tls backend uses the same logic as OpenSSL on all platforms.
|
||||||
@@ -139,6 +142,7 @@ pub struct TlsParametersBuilder {
|
|||||||
domain: String,
|
domain: String,
|
||||||
cert_store: CertificateStore,
|
cert_store: CertificateStore,
|
||||||
root_certs: Vec<Certificate>,
|
root_certs: Vec<Certificate>,
|
||||||
|
identity: Option<Identity>,
|
||||||
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"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
@@ -152,6 +156,7 @@ impl TlsParametersBuilder {
|
|||||||
domain,
|
domain,
|
||||||
cert_store: CertificateStore::Default,
|
cert_store: CertificateStore::Default,
|
||||||
root_certs: Vec::new(),
|
root_certs: Vec::new(),
|
||||||
|
identity: None,
|
||||||
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"))]
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
@@ -167,12 +172,20 @@ impl TlsParametersBuilder {
|
|||||||
|
|
||||||
/// Add a custom root certificate
|
/// Add a custom root certificate
|
||||||
///
|
///
|
||||||
/// Can be used to safely connect to a server using a self signed certificate, for example.
|
/// Can be used to safely connect to a server using a self-signed certificate, for example.
|
||||||
pub fn add_root_certificate(mut self, cert: Certificate) -> Self {
|
pub fn add_root_certificate(mut self, cert: Certificate) -> Self {
|
||||||
self.root_certs.push(cert);
|
self.root_certs.push(cert);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a client certificate
|
||||||
|
///
|
||||||
|
/// Can be used to configure a client certificate to present to the server.
|
||||||
|
pub fn identify_with(mut self, identity: Identity) -> Self {
|
||||||
|
self.identity = Some(identity);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Controls whether certificates with an invalid hostname are accepted
|
/// Controls whether certificates with an invalid hostname are accepted
|
||||||
///
|
///
|
||||||
/// Defaults to `false`.
|
/// Defaults to `false`.
|
||||||
@@ -184,10 +197,11 @@ impl TlsParametersBuilder {
|
|||||||
/// including those from other sites, are trusted.
|
/// including those from other sites, are trusted.
|
||||||
///
|
///
|
||||||
/// This method introduces significant vulnerabilities to man-in-the-middle attacks.
|
/// This method introduces significant vulnerabilities to man-in-the-middle attacks.
|
||||||
///
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
/// Hostname verification can only be disabled with the `native-tls` TLS backend.
|
#[cfg_attr(
|
||||||
#[cfg(any(feature = "native-tls", feature = "boring-tls"))]
|
docsrs,
|
||||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "native-tls", feature = "boring-tls"))))]
|
doc(cfg(any(feature = "native-tls", feature = "rustls-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
|
||||||
@@ -275,6 +289,10 @@ impl TlsParametersBuilder {
|
|||||||
};
|
};
|
||||||
|
|
||||||
tls_builder.min_protocol_version(Some(min_tls_version));
|
tls_builder.min_protocol_version(Some(min_tls_version));
|
||||||
|
if let Some(identity) = self.identity {
|
||||||
|
tls_builder.identity(identity.native_tls);
|
||||||
|
}
|
||||||
|
|
||||||
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),
|
||||||
@@ -317,6 +335,15 @@ impl TlsParametersBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(identity) = self.identity {
|
||||||
|
tls_builder
|
||||||
|
.set_certificate(identity.boring_tls.0.as_ref())
|
||||||
|
.map_err(error::tls)?;
|
||||||
|
tls_builder
|
||||||
|
.set_private_key(identity.boring_tls.1.as_ref())
|
||||||
|
.map_err(error::tls)?;
|
||||||
|
}
|
||||||
|
|
||||||
let min_tls_version = match self.min_tls_version {
|
let min_tls_version = match self.min_tls_version {
|
||||||
TlsVersion::Tlsv10 => SslVersion::TLS1,
|
TlsVersion::Tlsv10 => SslVersion::TLS1,
|
||||||
TlsVersion::Tlsv11 => SslVersion::TLS1_1,
|
TlsVersion::Tlsv11 => SslVersion::TLS1_1,
|
||||||
@@ -352,51 +379,73 @@ impl TlsParametersBuilder {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let tls = ClientConfig::builder_with_protocol_versions(supported_versions);
|
let tls = ClientConfig::builder_with_protocol_versions(supported_versions);
|
||||||
|
let provider = rustls::crypto::CryptoProvider::get_default()
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| Arc::new(rustls::crypto::ring::default_provider()));
|
||||||
|
|
||||||
let tls = if self.accept_invalid_certs {
|
// Build TLS config
|
||||||
tls.dangerous()
|
let signature_algorithms = provider.signature_verification_algorithms;
|
||||||
.with_custom_certificate_verifier(Arc::new(InvalidCertsVerifier {}))
|
|
||||||
} else {
|
|
||||||
let mut root_cert_store = RootCertStore::empty();
|
|
||||||
|
|
||||||
#[cfg(feature = "rustls-native-certs")]
|
let mut root_cert_store = RootCertStore::empty();
|
||||||
fn load_native_roots(store: &mut RootCertStore) -> Result<(), Error> {
|
|
||||||
let native_certs = rustls_native_certs::load_native_certs().map_err(error::tls)?;
|
#[cfg(feature = "rustls-native-certs")]
|
||||||
let (added, ignored) = store.add_parsable_certificates(native_certs);
|
fn load_native_roots(store: &mut RootCertStore) -> Result<(), Error> {
|
||||||
#[cfg(feature = "tracing")]
|
let rustls_native_certs::CertificateResult { certs, errors, .. } =
|
||||||
tracing::debug!(
|
rustls_native_certs::load_native_certs();
|
||||||
"loaded platform certs with {added} valid and {ignored} ignored (invalid) certs"
|
let errors_len = errors.len();
|
||||||
);
|
|
||||||
Ok(())
|
let (added, ignored) = store.add_parsable_certificates(certs);
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
tracing::debug!(
|
||||||
|
"loaded platform certs with {errors_len} failing to load, {added} valid and {ignored} ignored (invalid) certs"
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
fn load_webpki_roots(store: &mut RootCertStore) {
|
||||||
|
store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.cert_store {
|
||||||
|
CertificateStore::Default => {
|
||||||
|
#[cfg(feature = "rustls-native-certs")]
|
||||||
|
load_native_roots(&mut root_cert_store)?;
|
||||||
|
#[cfg(not(feature = "rustls-native-certs"))]
|
||||||
|
load_webpki_roots(&mut root_cert_store);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
fn load_webpki_roots(store: &mut RootCertStore) {
|
CertificateStore::WebpkiRoots => {
|
||||||
store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
load_webpki_roots(&mut root_cert_store);
|
||||||
}
|
|
||||||
|
|
||||||
match self.cert_store {
|
|
||||||
CertificateStore::Default => {
|
|
||||||
#[cfg(feature = "rustls-native-certs")]
|
|
||||||
load_native_roots(&mut root_cert_store)?;
|
|
||||||
#[cfg(not(feature = "rustls-native-certs"))]
|
|
||||||
load_webpki_roots(&mut root_cert_store);
|
|
||||||
}
|
|
||||||
#[cfg(feature = "rustls-tls")]
|
|
||||||
CertificateStore::WebpkiRoots => {
|
|
||||||
load_webpki_roots(&mut root_cert_store);
|
|
||||||
}
|
|
||||||
CertificateStore::None => {}
|
|
||||||
}
|
|
||||||
for cert in self.root_certs {
|
|
||||||
for rustls_cert in cert.rustls {
|
|
||||||
root_cert_store.add(rustls_cert).map_err(error::tls)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
CertificateStore::None => {}
|
||||||
|
}
|
||||||
|
for cert in self.root_certs {
|
||||||
|
for rustls_cert in cert.rustls {
|
||||||
|
root_cert_store.add(rustls_cert).map_err(error::tls)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tls = if self.accept_invalid_certs || self.accept_invalid_hostnames {
|
||||||
|
let verifier = InvalidCertsVerifier {
|
||||||
|
ignore_invalid_hostnames: self.accept_invalid_hostnames,
|
||||||
|
ignore_invalid_certs: self.accept_invalid_certs,
|
||||||
|
roots: root_cert_store,
|
||||||
|
signature_algorithms,
|
||||||
|
};
|
||||||
|
tls.dangerous()
|
||||||
|
.with_custom_certificate_verifier(Arc::new(verifier))
|
||||||
|
} else {
|
||||||
tls.with_root_certificates(root_cert_store)
|
tls.with_root_certificates(root_cert_store)
|
||||||
};
|
};
|
||||||
let tls = tls.with_no_client_auth();
|
|
||||||
|
let tls = if let Some(identity) = self.identity {
|
||||||
|
let (client_certificates, private_key) = identity.rustls_tls;
|
||||||
|
tls.with_client_auth_cert(client_certificates, private_key)
|
||||||
|
.map_err(error::tls)?
|
||||||
|
} else {
|
||||||
|
tls.with_no_client_auth()
|
||||||
|
};
|
||||||
|
|
||||||
Ok(TlsParameters {
|
Ok(TlsParameters {
|
||||||
connector: InnerTlsParameters::RustlsTls(Arc::new(tls)),
|
connector: InnerTlsParameters::RustlsTls(Arc::new(tls)),
|
||||||
@@ -461,7 +510,7 @@ impl TlsParameters {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A client certificate that can be used with [`TlsParametersBuilder::add_root_certificate`]
|
/// A certificate that can be used with [`TlsParametersBuilder::add_root_certificate`]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
#[allow(missing_copy_implementations)]
|
#[allow(missing_copy_implementations)]
|
||||||
pub struct Certificate {
|
pub struct Certificate {
|
||||||
@@ -528,20 +577,109 @@ impl Debug for Certificate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An identity that can be used with [`TlsParametersBuilder::identify_with`]
|
||||||
|
#[allow(missing_copy_implementations)]
|
||||||
|
pub struct Identity {
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
native_tls: native_tls::Identity,
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
rustls_tls: (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>),
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
boring_tls: (boring::x509::X509, PKey<boring::pkey::Private>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Identity {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Identity").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Identity {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Identity {
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
native_tls: self.native_tls.clone(),
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
rustls_tls: (self.rustls_tls.0.clone(), self.rustls_tls.1.clone_key()),
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
boring_tls: (self.boring_tls.0.clone(), self.boring_tls.1.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
|
||||||
|
impl Identity {
|
||||||
|
pub fn from_pem(pem: &[u8], key: &[u8]) -> Result<Self, Error> {
|
||||||
|
Ok(Self {
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
native_tls: Identity::from_pem_native_tls(pem, key)?,
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
rustls_tls: Identity::from_pem_rustls_tls(pem, key)?,
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
boring_tls: Identity::from_pem_boring_tls(pem, key)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
fn from_pem_native_tls(pem: &[u8], key: &[u8]) -> Result<native_tls::Identity, Error> {
|
||||||
|
native_tls::Identity::from_pkcs8(pem, key).map_err(error::tls)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rustls-tls")]
|
||||||
|
fn from_pem_rustls_tls(
|
||||||
|
pem: &[u8],
|
||||||
|
key: &[u8],
|
||||||
|
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>), Error> {
|
||||||
|
let mut key = key;
|
||||||
|
let key = rustls_pemfile::private_key(&mut key).unwrap().unwrap();
|
||||||
|
Ok((vec![pem.to_owned().into()], key))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "boring-tls")]
|
||||||
|
fn from_pem_boring_tls(
|
||||||
|
pem: &[u8],
|
||||||
|
key: &[u8],
|
||||||
|
) -> Result<(boring::x509::X509, PKey<boring::pkey::Private>), Error> {
|
||||||
|
let cert = boring::x509::X509::from_pem(pem).map_err(error::tls)?;
|
||||||
|
let key = boring::pkey::PKey::private_key_from_pem(key).map_err(error::tls)?;
|
||||||
|
Ok((cert, key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct InvalidCertsVerifier;
|
struct InvalidCertsVerifier {
|
||||||
|
ignore_invalid_hostnames: bool,
|
||||||
|
ignore_invalid_certs: bool,
|
||||||
|
roots: RootCertStore,
|
||||||
|
signature_algorithms: WebPkiSupportedAlgorithms,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "rustls-tls")]
|
#[cfg(feature = "rustls-tls")]
|
||||||
impl ServerCertVerifier for InvalidCertsVerifier {
|
impl ServerCertVerifier for InvalidCertsVerifier {
|
||||||
fn verify_server_cert(
|
fn verify_server_cert(
|
||||||
&self,
|
&self,
|
||||||
_end_entity: &CertificateDer<'_>,
|
end_entity: &CertificateDer<'_>,
|
||||||
_intermediates: &[CertificateDer<'_>],
|
intermediates: &[CertificateDer<'_>],
|
||||||
_server_name: &ServerName<'_>,
|
server_name: &ServerName<'_>,
|
||||||
_ocsp_response: &[u8],
|
_ocsp_response: &[u8],
|
||||||
_now: UnixTime,
|
now: UnixTime,
|
||||||
) -> Result<ServerCertVerified, TlsError> {
|
) -> Result<ServerCertVerified, TlsError> {
|
||||||
|
let cert = ParsedCertificate::try_from(end_entity)?;
|
||||||
|
|
||||||
|
if !self.ignore_invalid_certs {
|
||||||
|
rustls::client::verify_server_cert_signed_by_trust_anchor(
|
||||||
|
&cert,
|
||||||
|
&self.roots,
|
||||||
|
intermediates,
|
||||||
|
now,
|
||||||
|
self.signature_algorithms.all,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.ignore_invalid_hostnames {
|
||||||
|
rustls::client::verify_server_name(&cert, server_name)?;
|
||||||
|
}
|
||||||
Ok(ServerCertVerified::assertion())
|
Ok(ServerCertVerified::assertion())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ impl Transport for SmtpTransport {
|
|||||||
let result = conn.send(envelope, email)?;
|
let result = conn.send(envelope, email)?;
|
||||||
|
|
||||||
#[cfg(not(feature = "pool"))]
|
#[cfg(not(feature = "pool"))]
|
||||||
conn.quit()?;
|
conn.abort();
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user