Compare commits

...

24 Commits

Author SHA1 Message Date
Paolo Barbolini
c40af78809 Prepare 0.11.5 (#951) 2024-03-25 17:59:47 +01:00
Paolo Barbolini
6d2e0d5046 Bump rustls to v0.23 (#950) 2024-03-22 20:09:27 +01:00
dependabot[bot]
c64cb0ff2e Bump mio from 0.8.10 to 0.8.11 (#946)
Bumps [mio](https://github.com/tokio-rs/mio) from 0.8.10 to 0.8.11.
- [Release notes](https://github.com/tokio-rs/mio/releases)
- [Changelog](https://github.com/tokio-rs/mio/blob/master/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/mio/compare/v0.8.10...v0.8.11)

---
updated-dependencies:
- dependency-name: mio
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-04 22:38:54 +01:00
Paolo Barbolini
10d7b197ed chore(cargo): bump base64 to v0.22 (#945) 2024-03-02 11:34:57 +01:00
Viktor Szépe
fb54855d5f Fix typos (#944) 2024-02-21 14:49:42 +01:00
ciffelia
157c4fb5ae docs(transport): fix error in "Available transports" table (#943) 2024-02-18 17:19:27 +01:00
Hodu Mayo
1196e332ee feat(transport-smtp): Support to SASL draft login challenge (#911) 2024-02-14 21:18:56 +01:00
Alexis Mousset
75770f7bc6 Add conversion from SMTP code to integer (#941) 2024-02-14 21:01:21 +01:00
Alexis Mousset
76d0929c94 Add a Cargo.lock (#942) 2024-02-14 20:50:54 +01:00
Paolo Barbolini
c3d00051b2 Prepare 0.11.4 (#936) 2024-01-28 08:08:26 +01:00
Birk Tjelmeland
12580d82f4 style(email): Change Part::body_raw to Part::format_body 2024-01-28 07:54:16 +01:00
Birk Tjelmeland
f7849078b8 fix(email): Fix mimebody DKIM body-hash computation 2024-01-28 07:54:16 +01:00
Paolo Barbolini
f2c94cdf4d chore(cargo): bump maud to v0.26 (#935) 2024-01-25 20:08:32 +01:00
Paolo Barbolini
74f64b81ab test(transport/smtp): test credentials percent decoding from URL (#934) 2024-01-25 20:05:31 +01:00
42triangles
39c71dbfd2 transport/smtp: percent decode credentials in URL (#932) 2024-01-12 11:43:30 +00:00
Paolo Barbolini
c1bf5dfda1 Prepare 0.11.3 (#929) 2024-01-02 18:45:34 +01:00
Paolo Barbolini
1c1fef8055 Drop once_cell dependency in favor of OnceLock from std (#928) 2024-01-02 11:53:47 +01:00
Paolo Barbolini
1540f16015 Upgrade rustls to v0.22 (#921) 2024-01-02 11:41:16 +01:00
Tobias Bieniek
330daa1173 transport/smtp: Implement Debug trait (#925) 2023-12-17 09:20:51 +01:00
Tobias Bieniek
47f2fe0750 transport/file: Derive Clone impls (#924) 2023-12-16 21:30:57 +01:00
Paolo Barbolini
8b6cee30ee Prepare 0.11.2 (#919) 2023-11-23 09:49:21 +01:00
Paolo Barbolini
62c16e90ef Bump idna to v0.5 (#918) 2023-11-23 08:23:25 +00:00
Paolo Barbolini
e0494a5f9d Bump boringssl crates to v4 (#915) 2023-11-19 11:49:43 +01:00
Paolo Barbolini
8c3bffa728 Bump MSRV to 1.70 (#916) 2023-11-19 11:42:49 +01:00
20 changed files with 2911 additions and 107 deletions

View File

@@ -75,8 +75,8 @@ jobs:
rust: stable
- name: beta
rust: beta
- name: 1.65.0
rust: 1.65.0
- name: '1.70'
rust: '1.70'
steps:
- name: Checkout

1
.gitignore vendored
View File

@@ -4,4 +4,3 @@
lettre.sublime-*
lettre.iml
target/
/Cargo.lock

View File

@@ -1,3 +1,72 @@
<a name="v0.11.5"></a>
### v0.11.5 (2024-03-25)
#### Features
* Support SMTP SASL draft login challenge ([#911])
* Add conversion from SMTP response code to integer ([#941])
#### Misc
* Upgrade `rustls` to v0.23 ([#950])
* Bump `base64` to v0.22 ([#945])
* Fix typos in documentation ([#943], [#944])
* Add `Cargo.lock` ([#942])
[#911]: https://github.com/lettre/lettre/pull/911
[#941]: https://github.com/lettre/lettre/pull/941
[#942]: https://github.com/lettre/lettre/pull/942
[#943]: https://github.com/lettre/lettre/pull/943
[#944]: https://github.com/lettre/lettre/pull/944
[#945]: https://github.com/lettre/lettre/pull/945
[#950]: https://github.com/lettre/lettre/pull/950
<a name="v0.11.4"></a>
### v0.11.4 (2024-01-28)
#### Bug fixes
* Percent decode credentials in SMTP connect URL ([#932], [#934])
* Fix mimebody DKIM body-hash computation ([#923])
[#923]: https://github.com/lettre/lettre/pull/923
[#932]: https://github.com/lettre/lettre/pull/932
[#934]: https://github.com/lettre/lettre/pull/934
<a name="v0.11.3"></a>
### v0.11.3 (2024-01-02)
#### Features
* Derive `Clone` for `FileTransport` and `AsyncFileTransport` ([#924])
* Derive `Debug` for `SmtpTransport` ([#925])
#### Misc
* Upgrade `rustls` to v0.22 ([#921])
* Drop once_cell dependency in favor of OnceLock from std ([#928])
[#921]: https://github.com/lettre/lettre/pull/921
[#924]: https://github.com/lettre/lettre/pull/924
[#925]: https://github.com/lettre/lettre/pull/925
[#928]: https://github.com/lettre/lettre/pull/928
<a name="v0.11.2"></a>
### v0.11.2 (2023-11-23)
#### Upgrade notes
* MSRV is now 1.70 ([#916])
#### Misc
* Bump `idna` to v0.5 ([#918])
* Bump `boring` and `tokio-boring` to v4 ([#915])
[#915]: https://github.com/lettre/lettre/pull/915
[#916]: https://github.com/lettre/lettre/pull/916
[#918]: https://github.com/lettre/lettre/pull/918
<a name="v0.11.1"></a>
### v0.11.1 (2023-10-24)
@@ -198,7 +267,7 @@ Several breaking changes were made between 0.9 and 0.10, but changes should be s
* Update `hostname` to 0.3
* Update to `nom` 6
* Replace `log` with `tracing`
* Move CI to Github Actions
* Move CI to GitHub Actions
* Use criterion for benchmarks
<a name="v0.9.2"></a>

2661
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
[package]
name = "lettre"
# remember to update html_root_url and README.md (Cargo.toml example and deps.rs badge)
version = "0.11.1"
version = "0.11.5"
description = "Email client"
readme = "README.md"
homepage = "https://lettre.rs"
@@ -11,7 +11,7 @@ authors = ["Alexis Mousset <contact@amousset.me>", "Paolo Barbolini <paolo@paolo
categories = ["email", "network-programming"]
keywords = ["email", "smtp", "mailer", "message", "sendmail"]
edition = "2021"
rust-version = "1.65"
rust-version = "1.70"
[badges]
is-it-maintained-issue-resolution = { repository = "lettre/lettre" }
@@ -20,8 +20,7 @@ maintenance = { status = "actively-developed" }
[dependencies]
chumsky = "0.9"
idna = "0.4"
once_cell = { version = "1", optional = true }
idna = "0.5"
tracing = { version = "0.1.16", default-features = false, features = ["std"], optional = true } # feature
# builder
@@ -29,7 +28,7 @@ httpdate = { version = "1", optional = true }
mime = { version = "0.3.4", optional = true }
fastrand = { version = "2.0", optional = true }
quoted_printable = { version = "0.5", optional = true }
base64 = { version = "0.21", optional = true }
base64 = { version = "0.22", optional = true }
email-encoding = { version = "0.2", optional = true }
# file transport
@@ -42,14 +41,15 @@ nom = { version = "7", optional = true }
hostname = { version = "0.3", optional = true } # feature
socket2 = { version = "0.5.1", optional = true }
url = { version = "2.4", optional = true }
percent-encoding = { version = "2.3", optional = true }
## tls
native-tls = { version = "0.2.5", optional = true } # feature
rustls = { version = "0.21", features = ["dangerous_configuration"], optional = true }
rustls-pemfile = { version = "1", optional = true }
rustls-native-certs = { version = "0.6.2", optional = true }
webpki-roots = { version = "0.25", optional = true }
boring = { version = "3", optional = true }
rustls = { version = "0.23.3", default-features = false, features = ["ring", "logging", "std", "tls12"], optional = true }
rustls-pemfile = { version = "2", optional = true }
rustls-native-certs = { version = "0.7", optional = true }
webpki-roots = { version = "0.26", optional = true }
boring = { version = "4", optional = true }
# async
futures-io = { version = "0.3.7", optional = true }
@@ -59,13 +59,13 @@ async-trait = { version = "0.1", optional = true }
## async-std
async-std = { version = "1.8", optional = true }
#async-native-tls = { version = "0.3.3", optional = true }
futures-rustls = { version = "0.24", optional = true }
futures-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12", "ring"], optional = true }
## tokio
tokio1_crate = { package = "tokio", version = "1", optional = true }
tokio1_native_tls_crate = { package = "tokio-native-tls", version = "0.3", optional = true }
tokio1_rustls = { package = "tokio-rustls", version = "0.24", optional = true }
tokio1_boring = { package = "tokio-boring", version = "3", optional = true }
tokio1_rustls = { package = "tokio-rustls", version = "0.26", default-features = false, features = ["logging", "tls12", "ring"], optional = true }
tokio1_boring = { package = "tokio-boring", version = "4", optional = true }
## dkim
sha2 = { version = "0.10", optional = true, features = ["oid"] }
@@ -85,7 +85,7 @@ walkdir = "2"
tokio1_crate = { package = "tokio", version = "1", features = ["macros", "rt-multi-thread"] }
async-std = { version = "1.8", features = ["attributes"] }
serde_json = "1"
maud = "0.25"
maud = "0.26"
[[bench]]
harness = false
@@ -104,7 +104,7 @@ mime03 = ["dep:mime"]
file-transport = ["dep:uuid", "tokio1_crate?/fs", "tokio1_crate?/io-util"]
file-transport-envelope = ["serde", "dep:serde_json", "file-transport"]
sendmail-transport = ["tokio1_crate?/process", "tokio1_crate?/io-util", "async-std?/unstable"]
smtp-transport = ["dep:base64", "dep:nom", "dep:socket2", "dep:once_cell", "dep:url", "tokio1_crate?/rt", "tokio1_crate?/time", "tokio1_crate?/net"]
smtp-transport = ["dep:base64", "dep:nom", "dep:socket2", "dep:url", "dep:percent-encoding", "tokio1_crate?/rt", "tokio1_crate?/time", "tokio1_crate?/net"]
pool = ["dep:futures-util"]

View File

@@ -28,8 +28,8 @@
</div>
<div align="center">
<a href="https://deps.rs/crate/lettre/0.11.1">
<img src="https://deps.rs/crate/lettre/0.11.1/status.svg"
<a href="https://deps.rs/crate/lettre/0.11.5">
<img src="https://deps.rs/crate/lettre/0.11.5/status.svg"
alt="dependency status" />
</a>
</div>
@@ -53,12 +53,12 @@ Lettre does not provide (for now):
## Supported Rust Versions
Lettre supports all Rust versions released in the last 6 months. At the time of writing
the minimum supported Rust version is 1.65, but this could change at any time either from
the minimum supported Rust version is 1.70, but this could change at any time either from
one of our dependencies bumping their MSRV or by a new patch release of lettre.
## Example
This library requires Rust 1.65 or newer.
This library requires Rust 1.70 or newer.
To use this library, add the following to your `Cargo.toml`:
```toml

View File

@@ -6,7 +6,7 @@
//! * Secure defaults
//! * Async support
//!
//! Lettre requires Rust 1.65 or newer.
//! Lettre requires Rust 1.70 or newer.
//!
//! ## Features
//!
@@ -109,7 +109,7 @@
//! [mime 0.3]: https://docs.rs/mime/0.3
//! [DKIM]: https://datatracker.ietf.org/doc/html/rfc6376
#![doc(html_root_url = "https://docs.rs/crate/lettre/0.11.1")]
#![doc(html_root_url = "https://docs.rs/crate/lettre/0.11.5")]
#![doc(html_favicon_url = "https://lettre.rs/favicon.ico")]
#![doc(html_logo_url = "https://avatars0.githubusercontent.com/u/15113230?v=4")]
#![forbid(unsafe_code)]

View File

@@ -17,6 +17,16 @@ pub(super) enum Part {
Multi(MultiPart),
}
impl Part {
#[cfg(feature = "dkim")]
pub(super) fn format_body(&self, out: &mut Vec<u8>) {
match self {
Part::Single(part) => part.format_body(out),
Part::Multi(part) => part.format_body(out),
}
}
}
impl EmailFormat for Part {
fn format(&self, out: &mut Vec<u8>) {
match self {
@@ -132,6 +142,12 @@ impl SinglePart {
self.format(&mut out);
out
}
/// Format only the signlepart body
fn format_body(&self, out: &mut Vec<u8>) {
out.extend_from_slice(&self.body);
out.extend_from_slice(b"\r\n");
}
}
impl EmailFormat for SinglePart {
@@ -139,8 +155,7 @@ impl EmailFormat for SinglePart {
write!(out, "{}", self.headers)
.expect("A Write implementation panicked while formatting headers");
out.extend_from_slice(b"\r\n");
out.extend_from_slice(&self.body);
out.extend_from_slice(b"\r\n");
self.format_body(out);
}
}
@@ -373,14 +388,9 @@ impl MultiPart {
self.format(&mut out);
out
}
}
impl EmailFormat for MultiPart {
fn format(&self, out: &mut Vec<u8>) {
write!(out, "{}", self.headers)
.expect("A Write implementation panicked while formatting headers");
out.extend_from_slice(b"\r\n");
/// Format only the multipart body
fn format_body(&self, out: &mut Vec<u8>) {
let boundary = self.boundary();
for part in &self.parts {
@@ -396,6 +406,15 @@ impl EmailFormat for MultiPart {
}
}
impl EmailFormat for MultiPart {
fn format(&self, out: &mut Vec<u8>) {
write!(out, "{}", self.headers)
.expect("A Write implementation panicked while formatting headers");
out.extend_from_slice(b"\r\n");
self.format_body(out);
}
}
#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;

View File

@@ -525,7 +525,7 @@ impl Message {
pub(crate) fn body_raw(&self) -> Vec<u8> {
let mut out = Vec::new();
match &self.body {
MessageBody::Mime(p) => p.format(&mut out),
MessageBody::Mime(p) => p.format_body(&mut out),
MessageBody::Raw(r) => out.extend_from_slice(r),
};
out.extend_from_slice(b"\r\n");

View File

@@ -157,7 +157,7 @@ mod error;
type Id = String;
/// Writes the content and the envelope information to a file
#[derive(Debug)]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(docsrs, doc(cfg(feature = "file-transport")))]
pub struct FileTransport {
@@ -167,7 +167,7 @@ pub struct FileTransport {
}
/// Asynchronously writes the content and the envelope information to a file
#[derive(Debug)]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio1", feature = "async-std1"))))]
#[cfg(any(feature = "async-std1", feature = "tokio1"))]

View File

@@ -32,7 +32,7 @@
//! | [`smtp`] | SMTP | [`SmtpTransport`] | [`AsyncSmtpTransport`] | Uses the SMTP protocol to send emails to a relay server |
//! | [`sendmail`] | Sendmail | [`SendmailTransport`] | [`AsyncSendmailTransport`] | Uses the `sendmail` command to send emails |
//! | [`file`] | File | [`FileTransport`] | [`AsyncFileTransport`] | Saves the email as an `.eml` file |
//! | [`stub`] | Debug | [`StubTransport`] | [`StubTransport`] | Drops the email - Useful for debugging |
//! | [`stub`] | Debug | [`StubTransport`] | [`AsyncStubTransport`] | Drops the email - Useful for debugging |
//!
//! ## Building an email
//!
@@ -97,6 +97,7 @@
//! [`FileTransport`]: crate::FileTransport
//! [`AsyncFileTransport`]: crate::AsyncFileTransport
//! [`StubTransport`]: crate::transport::stub::StubTransport
//! [`AsyncStubTransport`]: crate::transport::stub::AsyncStubTransport
#[cfg(any(feature = "async-std1", feature = "tokio1"))]
use async_trait::async_trait;

View File

@@ -98,11 +98,13 @@ impl Mechanism {
let decoded_challenge = challenge
.ok_or_else(|| error::client("This mechanism does expect a challenge"))?;
if ["User Name", "Username:", "Username"].contains(&decoded_challenge) {
if ["User Name", "Username:", "Username", "User Name\0"]
.contains(&decoded_challenge)
{
return Ok(credentials.authentication_identity.clone());
}
if ["Password", "Password:"].contains(&decoded_challenge) {
if ["Password", "Password:", "Password\0"].contains(&decoded_challenge) {
return Ok(credentials.secret.clone());
}

View File

@@ -65,7 +65,7 @@ impl AsyncSmtpConnection {
/// If `tls_parameters` is `Some`, then the connection will use Implicit TLS (sometimes
/// referred to as `SMTPS`). See also [`AsyncSmtpConnection::starttls`].
///
/// If `local_addres` is `Some`, then the address provided shall be used to bind the
/// If `local_address` is `Some`, then the address provided shall be used to bind the
/// connection to a specific local address using [`tokio1_crate::net::TcpSocket::bind`].
///
/// Sends EHLO and parses server information

View File

@@ -16,6 +16,8 @@ use futures_io::{
};
#[cfg(feature = "async-std1-rustls-tls")]
use futures_rustls::client::TlsStream as AsyncStd1RustlsTlsStream;
#[cfg(any(feature = "tokio1-rustls-tls", feature = "async-std1-rustls-tls"))]
use rustls::pki_types::ServerName;
#[cfg(feature = "tokio1-boring-tls")]
use tokio1_boring::SslStream as Tokio1SslStream;
#[cfg(feature = "tokio1")]
@@ -350,7 +352,6 @@ impl AsyncNetworkStream {
#[cfg(feature = "tokio1-rustls-tls")]
return {
use rustls::ServerName;
use tokio1_rustls::TlsConnector;
let domain = ServerName::try_from(domain.as_str())
@@ -358,7 +359,7 @@ impl AsyncNetworkStream {
let connector = TlsConnector::from(config);
let stream = connector
.connect(domain, tcp_stream)
.connect(domain.to_owned(), tcp_stream)
.await
.map_err(error::connection)?;
Ok(InnerAsyncNetworkStream::Tokio1RustlsTls(stream))
@@ -424,14 +425,13 @@ impl AsyncNetworkStream {
#[cfg(feature = "async-std1-rustls-tls")]
return {
use futures_rustls::TlsConnector;
use rustls::ServerName;
let domain = ServerName::try_from(domain.as_str())
.map_err(|_| error::connection("domain isn't a valid DNS name"))?;
let connector = TlsConnector::from(config);
let stream = connector
.connect(domain, tcp_stream)
.connect(domain.to_owned(), tcp_stream)
.await
.map_err(error::connection)?;
Ok(InnerAsyncNetworkStream::AsyncStd1RustlsTls(stream))
@@ -486,8 +486,7 @@ impl AsyncNetworkStream {
.unwrap()
.first()
.unwrap()
.clone()
.0),
.to_vec()),
#[cfg(feature = "tokio1-boring-tls")]
InnerAsyncNetworkStream::Tokio1BoringTls(stream) => Ok(stream
.ssl()
@@ -509,8 +508,7 @@ impl AsyncNetworkStream {
.unwrap()
.first()
.unwrap()
.clone()
.0),
.to_vec()),
InnerAsyncNetworkStream::None => panic!("InnerNetworkStream::None must never be built"),
}
}

View File

@@ -12,7 +12,7 @@ use boring::ssl::SslStream;
#[cfg(feature = "native-tls")]
use native_tls::TlsStream;
#[cfg(feature = "rustls-tls")]
use rustls::{ClientConnection, ServerName, StreamOwned};
use rustls::{pki_types::ServerName, ClientConnection, StreamOwned};
use socket2::{Domain, Protocol, Type};
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
@@ -189,7 +189,7 @@ impl NetworkStream {
InnerTlsParameters::RustlsTls(connector) => {
let domain = ServerName::try_from(tls_parameters.domain())
.map_err(|_| error::connection("domain isn't a valid DNS name"))?;
let connection = ClientConnection::new(Arc::clone(connector), domain)
let connection = ClientConnection::new(Arc::clone(connector), domain.to_owned())
.map_err(error::connection)?;
let stream = StreamOwned::new(connection, tcp_stream);
InnerNetworkStream::RustlsTls(stream)
@@ -241,8 +241,7 @@ impl NetworkStream {
.unwrap()
.first()
.unwrap()
.clone()
.0),
.to_vec()),
#[cfg(feature = "boring-tls")]
InnerNetworkStream::BoringTls(stream) => Ok(stream
.ssl()

View File

@@ -1,6 +1,6 @@
use std::fmt::{self, Debug};
#[cfg(feature = "rustls-tls")]
use std::{sync::Arc, time::SystemTime};
use std::{io, sync::Arc};
#[cfg(feature = "boring-tls")]
use boring::{
@@ -11,8 +11,10 @@ use boring::{
use native_tls::{Protocol, TlsConnector};
#[cfg(feature = "rustls-tls")]
use rustls::{
client::{ServerCertVerified, ServerCertVerifier, WebPkiVerifier},
ClientConfig, Error as TlsError, RootCertStore, ServerName,
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
crypto::{verify_tls12_signature, verify_tls13_signature},
pki_types::{CertificateDer, ServerName, UnixTime},
ClientConfig, DigitallySignedStruct, Error as TlsError, RootCertStore, SignatureScheme,
};
#[cfg(any(feature = "native-tls", feature = "rustls-tls", feature = "boring-tls"))]
@@ -337,8 +339,6 @@ impl TlsParametersBuilder {
#[cfg(feature = "rustls-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-tls")))]
pub fn build_rustls(self) -> Result<TlsParameters, Error> {
let tls = ClientConfig::builder();
let just_version3 = &[&rustls::version::TLS13];
let supported_versions = match self.min_tls_version {
TlsVersion::Tlsv10 => {
@@ -351,50 +351,28 @@ impl TlsParametersBuilder {
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 = ClientConfig::builder_with_protocol_versions(supported_versions);
let tls = if self.accept_invalid_certs {
tls.with_custom_certificate_verifier(Arc::new(InvalidCertsVerifier {}))
tls.dangerous()
.with_custom_certificate_verifier(Arc::new(InvalidCertsVerifier {}))
} else {
let mut root_cert_store = RootCertStore::empty();
#[cfg(feature = "rustls-native-certs")]
fn load_native_roots(store: &mut RootCertStore) -> Result<(), Error> {
let native_certs = rustls_native_certs::load_native_certs().map_err(error::tls)?;
let mut valid_count = 0;
let mut invalid_count = 0;
for cert in native_certs {
match store.add(&rustls::Certificate(cert.0)) {
Ok(_) => valid_count += 1,
Err(err) => {
#[cfg(feature = "tracing")]
tracing::debug!("certificate parsing failed: {:?}", err);
invalid_count += 1;
}
}
}
let (added, ignored) = store.add_parsable_certificates(native_certs);
#[cfg(feature = "tracing")]
tracing::debug!(
"loaded platform certs with {valid_count} valid and {invalid_count} invalid certs"
"loaded platform certs with {added} valid and {ignored} ignored (invalid) certs"
);
Ok(())
}
#[cfg(feature = "rustls-tls")]
fn load_webpki_roots(store: &mut RootCertStore) {
// TODO: handle this in the rustls 0.22 upgrade
#[allow(deprecated)]
store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.iter().map(|ta| {
rustls::OwnedTrustAnchor::from_subject_spki_name_constraints(
ta.subject,
ta.spki,
ta.name_constraints,
)
}));
store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
}
match self.cert_store {
@@ -412,14 +390,11 @@ impl TlsParametersBuilder {
}
for cert in self.root_certs {
for rustls_cert in cert.rustls {
root_cert_store.add(&rustls_cert).map_err(error::tls)?;
root_cert_store.add(rustls_cert).map_err(error::tls)?;
}
}
tls.with_custom_certificate_verifier(Arc::new(WebPkiVerifier::new(
root_cert_store,
None,
)))
tls.with_root_certificates(root_cert_store)
};
let tls = tls.with_no_client_auth();
@@ -493,7 +468,7 @@ pub struct Certificate {
#[cfg(feature = "native-tls")]
native_tls: native_tls::Certificate,
#[cfg(feature = "rustls-tls")]
rustls: Vec<rustls::Certificate>,
rustls: Vec<CertificateDer<'static>>,
#[cfg(feature = "boring-tls")]
boring_tls: boring::x509::X509,
}
@@ -512,7 +487,7 @@ impl Certificate {
#[cfg(feature = "native-tls")]
native_tls: native_tls_cert,
#[cfg(feature = "rustls-tls")]
rustls: vec![rustls::Certificate(der)],
rustls: vec![der.into()],
#[cfg(feature = "boring-tls")]
boring_tls: boring_tls_cert,
})
@@ -532,10 +507,8 @@ impl Certificate {
let mut pem = Cursor::new(pem);
rustls_pemfile::certs(&mut pem)
.collect::<io::Result<Vec<_>>>()
.map_err(|_| error::tls("invalid certificates"))?
.into_iter()
.map(rustls::Certificate)
.collect::<Vec<_>>()
};
Ok(Self {
@@ -556,19 +529,53 @@ impl Debug for Certificate {
}
#[cfg(feature = "rustls-tls")]
#[derive(Debug)]
struct InvalidCertsVerifier;
#[cfg(feature = "rustls-tls")]
impl ServerCertVerifier for InvalidCertsVerifier {
fn verify_server_cert(
&self,
_end_entity: &rustls::Certificate,
_intermediates: &[rustls::Certificate],
_server_name: &ServerName,
_scts: &mut dyn Iterator<Item = &[u8]>,
_end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
_now: SystemTime,
_now: UnixTime,
) -> Result<ServerCertVerified, TlsError> {
Ok(ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TlsError> {
verify_tls12_signature(
message,
cert,
dss,
&rustls::crypto::ring::default_provider().signature_verification_algorithms,
)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, TlsError> {
verify_tls13_signature(
message,
cert,
dss,
&rustls::crypto::ring::default_provider().signature_verification_algorithms,
)
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
rustls::crypto::ring::default_provider()
.signature_verification_algorithms
.supported_schemes()
}
}

View File

@@ -112,7 +112,16 @@ pub(crate) fn from_connection_url<B: TransportBuilder>(connection_url: &str) ->
}
if let Some(password) = connection_url.password() {
let credentials = Credentials::new(connection_url.username().into(), password.into());
let percent_decode = |s: &str| {
percent_encoding::percent_decode_str(s)
.decode_utf8()
.map(|cow| cow.into_owned())
.map_err(error::connection)
};
let credentials = Credentials::new(
percent_decode(connection_url.username())?,
percent_decode(password)?,
);
builder = builder.credentials(credentials);
}

View File

@@ -2,7 +2,7 @@ use std::{
fmt::{self, Debug},
mem,
ops::{Deref, DerefMut},
sync::Arc,
sync::{Arc, OnceLock},
time::{Duration, Instant},
};
@@ -10,7 +10,6 @@ use futures_util::{
lock::Mutex,
stream::{self, StreamExt},
};
use once_cell::sync::OnceCell;
use super::{
super::{client::AsyncSmtpConnection, Error},
@@ -22,7 +21,7 @@ pub struct Pool<E: Executor> {
config: PoolConfig,
connections: Mutex<Vec<ParkedConnection>>,
client: AsyncSmtpClient<E>,
handle: OnceCell<E::Handle>,
handle: OnceLock<E::Handle>,
}
struct ParkedConnection {
@@ -41,7 +40,7 @@ impl<E: Executor> Pool<E> {
config,
connections: Mutex::new(Vec::new()),
client,
handle: OnceCell::new(),
handle: OnceLock::new(),
});
{

View File

@@ -132,6 +132,12 @@ impl Code {
}
}
impl From<Code> for u16 {
fn from(code: Code) -> Self {
code.detail as u16 + 10 * code.category as u16 + 100 * code.severity as u16
}
}
/// Contains an SMTP reply, with separated code and message
///
/// The text message is optional, only the code is mandatory
@@ -317,6 +323,17 @@ mod test {
assert_eq!(code.to_string(), "421");
}
#[test]
fn test_code_to_u16() {
let code = Code {
severity: Severity::TransientNegativeCompletion,
category: Category::Connections,
detail: Detail::One,
};
let c: u16 = code.into();
assert_eq!(c, 421);
}
#[test]
fn test_response_from_str() {
let raw_response = "250-me\r\n250-8BITMIME\r\n250-SIZE 42\r\n250 AUTH PLAIN CRAM-MD5\r\n";

View File

@@ -1,6 +1,6 @@
#[cfg(feature = "pool")]
use std::sync::Arc;
use std::time::Duration;
use std::{fmt::Debug, time::Duration};
#[cfg(feature = "pool")]
use super::pool::sync_impl::Pool;
@@ -38,6 +38,14 @@ impl Transport for SmtpTransport {
}
}
impl Debug for SmtpTransport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut builder = f.debug_struct("SmtpTransport");
builder.field("inner", &self.inner);
builder.finish()
}
}
impl SmtpTransport {
/// Simple and secure transport, using TLS connections to communicate with the SMTP server
///
@@ -373,6 +381,22 @@ mod tests {
assert!(matches!(builder.info.tls, Tls::Wrapper(_)));
assert_eq!(builder.info.server, "smtp.example.com");
let builder = SmtpTransport::from_url(
"smtps://user%40example.com:pa$$word%3F%22!@smtp.example.com:465",
)
.unwrap();
assert_eq!(builder.info.port, 465);
assert_eq!(
builder.info.credentials,
Some(Credentials::new(
"user@example.com".to_owned(),
"pa$$word?\"!".to_owned()
))
);
assert!(matches!(builder.info.tls, Tls::Wrapper(_)));
assert_eq!(builder.info.server, "smtp.example.com");
let builder =
SmtpTransport::from_url("smtp://username:password@smtp.example.com:587?tls=required")
.unwrap();