feat(transport-smtp): Add rustls support
This commit is contained in:
38
Cargo.toml
38
Cargo.toml
@@ -12,43 +12,47 @@ keywords = ["email", "smtp", "mailer"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
maintenance = { status = "actively-developed" }
|
|
||||||
is-it-maintained-issue-resolution = { repository = "lettre/lettre" }
|
is-it-maintained-issue-resolution = { repository = "lettre/lettre" }
|
||||||
is-it-maintained-open-issues = { repository = "lettre/lettre" }
|
is-it-maintained-open-issues = { repository = "lettre/lettre" }
|
||||||
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "^0.4"
|
|
||||||
nom = { version = "^5.0", optional = true }
|
|
||||||
bufstream = { version = "^0.1", optional = true }
|
|
||||||
native-tls = { version = "^0.2", optional = true }
|
|
||||||
base64 = { version = "^0.11", optional = true }
|
base64 = { version = "^0.11", optional = true }
|
||||||
|
bufstream = { version = "^0.1", optional = true }
|
||||||
|
email = { version = "^0.0.20", optional = true }
|
||||||
|
fast_chemail = "^0.9"
|
||||||
hostname = { version = "^0.2", optional = true }
|
hostname = { version = "^0.2", optional = true }
|
||||||
|
log = "^0.4"
|
||||||
|
mime = { version = "^0.3", optional = true }
|
||||||
|
native-tls = { version = "^0.2", optional = true }
|
||||||
|
nom = { version = "^5.0", optional = true }
|
||||||
|
r2d2 = { version = "^0.8", optional = true }
|
||||||
|
rustls = { version = "^0.16", optional = true }
|
||||||
serde = { version = "^1.0", optional = true, features = ["derive"] }
|
serde = { version = "^1.0", optional = true, features = ["derive"] }
|
||||||
serde_json = { version = "^1.0", optional = true }
|
serde_json = { version = "^1.0", optional = true }
|
||||||
fast_chemail = "^0.9"
|
|
||||||
r2d2 = { version = "^0.8", optional = true }
|
|
||||||
email = { version = "^0.0.20", optional = true }
|
|
||||||
mime = { version = "^0.3", optional = true }
|
|
||||||
time = { version = "^0.1", optional = true }
|
time = { version = "^0.1", optional = true }
|
||||||
uuid = { version = "^0.8", features = ["v4"], optional = true }
|
uuid = { version = "^0.8", features = ["v4"], optional = true }
|
||||||
|
webpki = { version = "^0.21", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
criterion = "^0.3"
|
||||||
env_logger = "^0.7"
|
env_logger = "^0.7"
|
||||||
glob = "^0.3"
|
glob = "^0.3"
|
||||||
criterion = "^0.3"
|
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "transport_smtp"
|
|
||||||
harness = false
|
harness = false
|
||||||
|
name = "transport_smtp"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["file-transport", "smtp-transport", "sendmail-transport", "builder"]
|
|
||||||
builder = ["email", "mime", "time", "base64", "uuid"]
|
builder = ["email", "mime", "time", "base64", "uuid"]
|
||||||
unstable = []
|
|
||||||
file-transport = ["serde", "serde_json"]
|
|
||||||
smtp-transport = ["bufstream", "native-tls", "base64", "nom", "hostname"]
|
|
||||||
sendmail-transport = []
|
|
||||||
connection-pool = ["r2d2"]
|
connection-pool = ["r2d2"]
|
||||||
|
default = ["file-transport", "smtp-transport", "sendmail-transport", "ssl-rustls", "builder"]
|
||||||
|
file-transport = ["serde", "serde_json"]
|
||||||
|
sendmail-transport = []
|
||||||
|
smtp-transport = ["bufstream", "base64", "nom", "hostname"]
|
||||||
|
ssl-native = ["native-tls"]
|
||||||
|
ssl-rustls = ["rustls", "webpki"]
|
||||||
|
unstable = []
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "smtp"
|
name = "smtp"
|
||||||
@@ -56,7 +60,7 @@ required-features = ["smtp-transport"]
|
|||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "smtp_gmail"
|
name = "smtp_gmail"
|
||||||
required-features = ["smtp-transport"]
|
required-features = ["smtp-transport", "native-tls"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "builder"
|
name = "builder"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use std::fmt::{self, Display, Formatter};
|
|||||||
pub const DEFAULT_ENCRYPTED_MECHANISMS: &[Mechanism] = &[Mechanism::Plain, Mechanism::Login];
|
pub const DEFAULT_ENCRYPTED_MECHANISMS: &[Mechanism] = &[Mechanism::Plain, Mechanism::Login];
|
||||||
|
|
||||||
/// Accepted authentication mechanisms on an unencrypted connection
|
/// Accepted authentication mechanisms on an unencrypted connection
|
||||||
|
// FIXME remove
|
||||||
pub const DEFAULT_UNENCRYPTED_MECHANISMS: &[Mechanism] = &[];
|
pub const DEFAULT_UNENCRYPTED_MECHANISMS: &[Mechanism] = &[];
|
||||||
|
|
||||||
/// Convertible to user credentials
|
/// Convertible to user credentials
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
//! SMTP client
|
//! SMTP client
|
||||||
|
|
||||||
use crate::smtp::authentication::{Credentials, Mechanism};
|
use crate::smtp::authentication::{Credentials, Mechanism};
|
||||||
use crate::smtp::client::net::{ClientTlsParameters, Connector, NetworkStream, Timeout};
|
use crate::smtp::client::net::ClientTlsParameters;
|
||||||
|
use crate::smtp::client::net::{Connector, NetworkStream, Timeout};
|
||||||
use crate::smtp::commands::*;
|
use crate::smtp::commands::*;
|
||||||
use crate::smtp::error::{Error, SmtpResult};
|
use crate::smtp::error::{Error, SmtpResult};
|
||||||
use crate::smtp::response::Response;
|
use crate::smtp::response::Response;
|
||||||
@@ -73,7 +74,7 @@ fn escape_crlf(string: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Structure that implements the SMTP client
|
/// Structure that implements the SMTP client
|
||||||
#[derive(Debug, Default)]
|
#[derive(Default)]
|
||||||
pub struct InnerClient<S: Write + Read = NetworkStream> {
|
pub struct InnerClient<S: Write + Read = NetworkStream> {
|
||||||
/// TCP stream between client and server
|
/// TCP stream between client and server
|
||||||
/// Value is None before connection
|
/// Value is None before connection
|
||||||
@@ -95,7 +96,7 @@ impl<S: Write + Read> InnerClient<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: Connector + Write + Read + Timeout + Debug> InnerClient<S> {
|
impl<S: Connector + Write + Read + Timeout> InnerClient<S> {
|
||||||
/// Closes the SMTP transaction if possible
|
/// Closes the SMTP transaction if possible
|
||||||
pub fn close(&mut self) {
|
pub fn close(&mut self) {
|
||||||
let _ = self.command(QuitCommand);
|
let _ = self.command(QuitCommand);
|
||||||
@@ -108,6 +109,7 @@ impl<S: Connector + Write + Read + Timeout + Debug> InnerClient<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Upgrades the underlying connection to SSL/TLS
|
/// Upgrades the underlying connection to SSL/TLS
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
pub fn upgrade_tls_stream(&mut self, tls_parameters: &ClientTlsParameters) -> io::Result<()> {
|
pub fn upgrade_tls_stream(&mut self, tls_parameters: &ClientTlsParameters) -> io::Result<()> {
|
||||||
match self.stream {
|
match self.stream {
|
||||||
Some(ref mut stream) => stream.get_mut().upgrade_tls(tls_parameters),
|
Some(ref mut stream) => stream.get_mut().upgrade_tls(tls_parameters),
|
||||||
@@ -155,6 +157,7 @@ impl<S: Connector + Write + Read + Timeout + Debug> InnerClient<S> {
|
|||||||
|
|
||||||
// Try to connect
|
// Try to connect
|
||||||
self.set_stream(Connector::connect(&server_addr, timeout, tls_parameters)?);
|
self.set_stream(Connector::connect(&server_addr, timeout, tls_parameters)?);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
//! A trait to represent a stream
|
//! A trait to represent a stream
|
||||||
|
|
||||||
use crate::smtp::client::mock::MockStream;
|
use crate::smtp::client::mock::MockStream;
|
||||||
|
use crate::smtp::error::Error;
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
use native_tls::{Protocol, TlsConnector, TlsStream};
|
use native_tls::{Protocol, TlsConnector, TlsStream};
|
||||||
use std::io::{self, ErrorKind, Read, Write};
|
#[cfg(feature = "rustls")]
|
||||||
|
use rustls::{ClientConfig, ClientSession};
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
use std::io::ErrorKind;
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
use std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4, TcpStream};
|
use std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4, TcpStream};
|
||||||
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
/// Parameters to use for secure clients
|
/// Parameters to use for secure clients
|
||||||
@@ -11,29 +18,43 @@ use std::time::Duration;
|
|||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct ClientTlsParameters {
|
pub struct ClientTlsParameters {
|
||||||
/// A connector from `native-tls`
|
/// A connector from `native-tls`
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
pub connector: TlsConnector,
|
pub connector: TlsConnector,
|
||||||
|
/// A client from `rustls`
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
|
pub connector: ClientConfig,
|
||||||
/// The domain name which is expected in the TLS certificate from the server
|
/// The domain name which is expected in the TLS certificate from the server
|
||||||
pub domain: String,
|
pub domain: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientTlsParameters {
|
impl ClientTlsParameters {
|
||||||
/// Creates a `ClientTlsParameters`
|
/// Creates a `ClientTlsParameters`
|
||||||
pub fn new(domain: String, connector: TlsConnector) -> ClientTlsParameters {
|
#[cfg(feature = "native-tls")]
|
||||||
|
pub fn new(domain: String, connector: TlsConnector) -> Self {
|
||||||
|
ClientTlsParameters { connector, domain }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a `ClientTlsParameters`
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
|
pub fn new(domain: String, connector: ClientConfig) -> Self {
|
||||||
ClientTlsParameters { connector, domain }
|
ClientTlsParameters { connector, domain }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Accepted protocols by default.
|
/// Accepted protocols by default.
|
||||||
/// This removes TLS 1.0 and 1.1 compared to tls-native defaults.
|
/// This removes TLS 1.0 and 1.1 compared to tls-native defaults.
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
pub const DEFAULT_TLS_MIN_PROTOCOL: Protocol = Protocol::Tlsv12;
|
pub const DEFAULT_TLS_MIN_PROTOCOL: Protocol = Protocol::Tlsv12;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
/// Represents the different types of underlying network streams
|
/// Represents the different types of underlying network streams
|
||||||
pub enum NetworkStream {
|
pub enum NetworkStream {
|
||||||
/// Plain TCP stream
|
/// Plain TCP stream
|
||||||
Tcp(TcpStream),
|
Tcp(TcpStream),
|
||||||
/// Encrypted TCP stream
|
/// Encrypted TCP stream
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
Tls(Box<TlsStream<TcpStream>>),
|
Tls(Box<TlsStream<TcpStream>>),
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
|
Tls(rustls::StreamOwned<ClientSession, TcpStream>),
|
||||||
/// Mock stream
|
/// Mock stream
|
||||||
Mock(MockStream),
|
Mock(MockStream),
|
||||||
}
|
}
|
||||||
@@ -43,6 +64,9 @@ impl NetworkStream {
|
|||||||
pub fn peer_addr(&self) -> io::Result<SocketAddr> {
|
pub fn peer_addr(&self) -> io::Result<SocketAddr> {
|
||||||
match *self {
|
match *self {
|
||||||
NetworkStream::Tcp(ref s) => s.peer_addr(),
|
NetworkStream::Tcp(ref s) => s.peer_addr(),
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
NetworkStream::Tls(ref s) => s.get_ref().peer_addr(),
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
NetworkStream::Tls(ref s) => s.get_ref().peer_addr(),
|
NetworkStream::Tls(ref s) => s.get_ref().peer_addr(),
|
||||||
NetworkStream::Mock(_) => Ok(SocketAddr::V4(SocketAddrV4::new(
|
NetworkStream::Mock(_) => Ok(SocketAddr::V4(SocketAddrV4::new(
|
||||||
Ipv4Addr::new(127, 0, 0, 1),
|
Ipv4Addr::new(127, 0, 0, 1),
|
||||||
@@ -55,6 +79,9 @@ impl NetworkStream {
|
|||||||
pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
|
pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
|
||||||
match *self {
|
match *self {
|
||||||
NetworkStream::Tcp(ref s) => s.shutdown(how),
|
NetworkStream::Tcp(ref s) => s.shutdown(how),
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
NetworkStream::Tls(ref s) => s.get_ref().shutdown(how),
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
NetworkStream::Tls(ref s) => s.get_ref().shutdown(how),
|
NetworkStream::Tls(ref s) => s.get_ref().shutdown(how),
|
||||||
NetworkStream::Mock(_) => Ok(()),
|
NetworkStream::Mock(_) => Ok(()),
|
||||||
}
|
}
|
||||||
@@ -65,6 +92,9 @@ impl Read for NetworkStream {
|
|||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
match *self {
|
match *self {
|
||||||
NetworkStream::Tcp(ref mut s) => s.read(buf),
|
NetworkStream::Tcp(ref mut s) => s.read(buf),
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
NetworkStream::Tls(ref mut s) => s.read(buf),
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
NetworkStream::Tls(ref mut s) => s.read(buf),
|
NetworkStream::Tls(ref mut s) => s.read(buf),
|
||||||
NetworkStream::Mock(ref mut s) => s.read(buf),
|
NetworkStream::Mock(ref mut s) => s.read(buf),
|
||||||
}
|
}
|
||||||
@@ -75,6 +105,9 @@ impl Write for NetworkStream {
|
|||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
match *self {
|
match *self {
|
||||||
NetworkStream::Tcp(ref mut s) => s.write(buf),
|
NetworkStream::Tcp(ref mut s) => s.write(buf),
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
NetworkStream::Tls(ref mut s) => s.write(buf),
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
NetworkStream::Tls(ref mut s) => s.write(buf),
|
NetworkStream::Tls(ref mut s) => s.write(buf),
|
||||||
NetworkStream::Mock(ref mut s) => s.write(buf),
|
NetworkStream::Mock(ref mut s) => s.write(buf),
|
||||||
}
|
}
|
||||||
@@ -83,6 +116,9 @@ impl Write for NetworkStream {
|
|||||||
fn flush(&mut self) -> io::Result<()> {
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
match *self {
|
match *self {
|
||||||
NetworkStream::Tcp(ref mut s) => s.flush(),
|
NetworkStream::Tcp(ref mut s) => s.flush(),
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
NetworkStream::Tls(ref mut s) => s.flush(),
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
NetworkStream::Tls(ref mut s) => s.flush(),
|
NetworkStream::Tls(ref mut s) => s.flush(),
|
||||||
NetworkStream::Mock(ref mut s) => s.flush(),
|
NetworkStream::Mock(ref mut s) => s.flush(),
|
||||||
}
|
}
|
||||||
@@ -96,9 +132,9 @@ pub trait Connector: Sized {
|
|||||||
addr: &SocketAddr,
|
addr: &SocketAddr,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
tls_parameters: Option<&ClientTlsParameters>,
|
tls_parameters: Option<&ClientTlsParameters>,
|
||||||
) -> io::Result<Self>;
|
) -> Result<Self, Error>;
|
||||||
/// Upgrades to TLS connection
|
/// Upgrades to TLS connection
|
||||||
fn upgrade_tls(&mut self, tls_parameters: &ClientTlsParameters) -> io::Result<()>;
|
fn upgrade_tls(&mut self, tls_parameters: &ClientTlsParameters) -> Result<(), Error>;
|
||||||
/// Is the NetworkStream encrypted
|
/// Is the NetworkStream encrypted
|
||||||
fn is_encrypted(&self) -> bool;
|
fn is_encrypted(&self) -> bool;
|
||||||
}
|
}
|
||||||
@@ -108,25 +144,35 @@ impl Connector for NetworkStream {
|
|||||||
addr: &SocketAddr,
|
addr: &SocketAddr,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
tls_parameters: Option<&ClientTlsParameters>,
|
tls_parameters: Option<&ClientTlsParameters>,
|
||||||
) -> io::Result<NetworkStream> {
|
) -> Result<NetworkStream, Error> {
|
||||||
let tcp_stream = match timeout {
|
let tcp_stream = match timeout {
|
||||||
Some(duration) => TcpStream::connect_timeout(addr, duration)?,
|
Some(duration) => TcpStream::connect_timeout(addr, duration)?,
|
||||||
None => TcpStream::connect(addr)?,
|
None => TcpStream::connect(addr)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
match tls_parameters {
|
match tls_parameters {
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
Some(context) => context
|
Some(context) => context
|
||||||
.connector
|
.connector
|
||||||
.connect(context.domain.as_ref(), tcp_stream)
|
.connect(context.domain.as_ref(), tcp_stream)
|
||||||
.map(|tls| NetworkStream::Tls(Box::new(tls)))
|
.map(|tls| NetworkStream::Tls(Box::new(tls)))
|
||||||
.map_err(|e| io::Error::new(ErrorKind::Other, e)),
|
.map_err(|e| io::Error::new(ErrorKind::Other, e)),
|
||||||
|
#[cfg(feature = "rustls")]
|
||||||
|
Some(context) => {
|
||||||
|
let domain = webpki::DNSNameRef::try_from_ascii_str(&context.domain)?;
|
||||||
|
|
||||||
|
Ok(NetworkStream::Tls(rustls::StreamOwned::new(
|
||||||
|
ClientSession::new(&Arc::new(context.connector.clone()), domain),
|
||||||
|
tcp_stream,
|
||||||
|
)))
|
||||||
|
}
|
||||||
None => Ok(NetworkStream::Tcp(tcp_stream)),
|
None => Ok(NetworkStream::Tcp(tcp_stream)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::match_same_arms))]
|
fn upgrade_tls(&mut self, tls_parameters: &ClientTlsParameters) -> Result<(), Error> {
|
||||||
fn upgrade_tls(&mut self, tls_parameters: &ClientTlsParameters) -> io::Result<()> {
|
|
||||||
*self = match *self {
|
*self = match *self {
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
NetworkStream::Tcp(ref mut stream) => match tls_parameters
|
NetworkStream::Tcp(ref mut stream) => match tls_parameters
|
||||||
.connector
|
.connector
|
||||||
.connect(tls_parameters.domain.as_ref(), stream.try_clone().unwrap())
|
.connect(tls_parameters.domain.as_ref(), stream.try_clone().unwrap())
|
||||||
@@ -134,19 +180,25 @@ impl Connector for NetworkStream {
|
|||||||
Ok(tls_stream) => NetworkStream::Tls(Box::new(tls_stream)),
|
Ok(tls_stream) => NetworkStream::Tls(Box::new(tls_stream)),
|
||||||
Err(err) => return Err(io::Error::new(ErrorKind::Other, err)),
|
Err(err) => return Err(io::Error::new(ErrorKind::Other, err)),
|
||||||
},
|
},
|
||||||
NetworkStream::Tls(_) => return Ok(()),
|
#[cfg(feature = "rustls")]
|
||||||
NetworkStream::Mock(_) => return Ok(()),
|
NetworkStream::Tcp(ref mut stream) => {
|
||||||
|
let domain = webpki::DNSNameRef::try_from_ascii_str(&tls_parameters.domain)?;
|
||||||
|
|
||||||
|
NetworkStream::Tls(rustls::StreamOwned::new(
|
||||||
|
ClientSession::new(&Arc::new(tls_parameters.connector.clone()), domain),
|
||||||
|
stream.try_clone().unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
NetworkStream::Tls(_) | NetworkStream::Mock(_) => return Ok(()),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::match_same_arms))]
|
|
||||||
fn is_encrypted(&self) -> bool {
|
fn is_encrypted(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
NetworkStream::Tcp(_) => false,
|
NetworkStream::Tcp(_) | NetworkStream::Mock(_) => false,
|
||||||
NetworkStream::Tls(_) => true,
|
NetworkStream::Tls(_) => true,
|
||||||
NetworkStream::Mock(_) => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use self::Error::*;
|
use self::Error::*;
|
||||||
use crate::smtp::response::{Response, Severity};
|
use crate::smtp::response::{Response, Severity};
|
||||||
use base64::DecodeError;
|
use base64::DecodeError;
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
use native_tls;
|
use native_tls;
|
||||||
use nom;
|
use nom;
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
@@ -35,9 +36,12 @@ pub enum Error {
|
|||||||
/// IO error
|
/// IO error
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
/// TLS error
|
/// TLS error
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
Tls(native_tls::Error),
|
Tls(native_tls::Error),
|
||||||
/// Parsing error
|
/// Parsing error
|
||||||
Parsing(nom::error::ErrorKind),
|
Parsing(nom::error::ErrorKind),
|
||||||
|
/// Invalid hostname
|
||||||
|
InvalidDNSName(webpki::InvalidDNSNameError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Error {
|
impl Display for Error {
|
||||||
@@ -66,8 +70,10 @@ impl StdError for Error {
|
|||||||
Resolution => "could not resolve hostname",
|
Resolution => "could not resolve hostname",
|
||||||
Client(err) => err,
|
Client(err) => err,
|
||||||
Io(ref err) => err.description(),
|
Io(ref err) => err.description(),
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
Tls(ref err) => err.description(),
|
Tls(ref err) => err.description(),
|
||||||
Parsing(ref err) => err.description(),
|
Parsing(ref err) => err.description(),
|
||||||
|
InvalidDNSName(ref err) => err.description(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,6 +82,7 @@ impl StdError for Error {
|
|||||||
ChallengeParsing(ref err) => Some(&*err),
|
ChallengeParsing(ref err) => Some(&*err),
|
||||||
Utf8Parsing(ref err) => Some(&*err),
|
Utf8Parsing(ref err) => Some(&*err),
|
||||||
Io(ref err) => Some(&*err),
|
Io(ref err) => Some(&*err),
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
Tls(ref err) => Some(&*err),
|
Tls(ref err) => Some(&*err),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
@@ -88,6 +95,7 @@ impl From<io::Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
impl From<native_tls::Error> for Error {
|
impl From<native_tls::Error> for Error {
|
||||||
fn from(err: native_tls::Error) -> Error {
|
fn from(err: native_tls::Error) -> Error {
|
||||||
Tls(err)
|
Tls(err)
|
||||||
@@ -116,6 +124,12 @@ impl From<FromUtf8Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<webpki::InvalidDNSNameError> for Error {
|
||||||
|
fn from(err: webpki::InvalidDNSNameError) -> Error {
|
||||||
|
InvalidDNSName(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Response> for Error {
|
impl From<Response> for Error {
|
||||||
fn from(response: Response) -> Error {
|
fn from(response: Response) -> Error {
|
||||||
match response.code.severity {
|
match response.code.severity {
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ use crate::smtp::authentication::{
|
|||||||
Credentials, Mechanism, DEFAULT_ENCRYPTED_MECHANISMS, DEFAULT_UNENCRYPTED_MECHANISMS,
|
Credentials, Mechanism, DEFAULT_ENCRYPTED_MECHANISMS, DEFAULT_UNENCRYPTED_MECHANISMS,
|
||||||
};
|
};
|
||||||
use crate::smtp::client::net::ClientTlsParameters;
|
use crate::smtp::client::net::ClientTlsParameters;
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
|
// TODO RUSTLS
|
||||||
use crate::smtp::client::net::DEFAULT_TLS_MIN_PROTOCOL;
|
use crate::smtp::client::net::DEFAULT_TLS_MIN_PROTOCOL;
|
||||||
use crate::smtp::client::InnerClient;
|
use crate::smtp::client::InnerClient;
|
||||||
use crate::smtp::commands::*;
|
use crate::smtp::commands::*;
|
||||||
@@ -24,6 +26,7 @@ use crate::smtp::error::{Error, SmtpResult};
|
|||||||
use crate::smtp::extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo};
|
use crate::smtp::extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo};
|
||||||
use crate::{SendableEmail, Transport};
|
use crate::{SendableEmail, Transport};
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
use native_tls::TlsConnector;
|
use native_tls::TlsConnector;
|
||||||
use std::net::{SocketAddr, ToSocketAddrs};
|
use std::net::{SocketAddr, ToSocketAddrs};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -134,6 +137,7 @@ impl SmtpClient {
|
|||||||
/// Simple and secure transport, should be used when possible.
|
/// Simple and secure transport, should be used when possible.
|
||||||
/// Creates an encrypted transport over submissions port, using the provided domain
|
/// Creates an encrypted transport over submissions port, using the provided domain
|
||||||
/// to validate TLS certificates.
|
/// to validate TLS certificates.
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
pub fn new_simple(domain: &str) -> Result<SmtpClient, Error> {
|
pub fn new_simple(domain: &str) -> Result<SmtpClient, Error> {
|
||||||
let mut tls_builder = TlsConnector::builder();
|
let mut tls_builder = TlsConnector::builder();
|
||||||
tls_builder.min_protocol_version(Some(DEFAULT_TLS_MIN_PROTOCOL));
|
tls_builder.min_protocol_version(Some(DEFAULT_TLS_MIN_PROTOCOL));
|
||||||
@@ -215,7 +219,7 @@ struct State {
|
|||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct SmtpTransport {
|
pub struct SmtpTransport {
|
||||||
/// Information about the server
|
/// Information about the server
|
||||||
/// Value is None before HELO/EHLO
|
/// Value is None before EHLO
|
||||||
server_info: Option<ServerInfo>,
|
server_info: Option<ServerInfo>,
|
||||||
/// SmtpTransport variable states
|
/// SmtpTransport variable states
|
||||||
state: State,
|
state: State,
|
||||||
@@ -302,16 +306,20 @@ impl<'a> SmtpTransport {
|
|||||||
(&ClientSecurity::Opportunistic(_), false) => (),
|
(&ClientSecurity::Opportunistic(_), false) => (),
|
||||||
(&ClientSecurity::None, _) => (),
|
(&ClientSecurity::None, _) => (),
|
||||||
(&ClientSecurity::Wrapper(_), _) => (),
|
(&ClientSecurity::Wrapper(_), _) => (),
|
||||||
|
#[cfg(feature = "native-tls")]
|
||||||
(&ClientSecurity::Opportunistic(ref tls_parameters), true)
|
(&ClientSecurity::Opportunistic(ref tls_parameters), true)
|
||||||
| (&ClientSecurity::Required(ref tls_parameters), true) => {
|
| (&ClientSecurity::Required(ref tls_parameters), true) => {
|
||||||
try_smtp!(self.client.command(StarttlsCommand), self);
|
try_smtp!(self.client.command(StarttlsCommand), self);
|
||||||
try_smtp!(self.client.upgrade_tls_stream(tls_parameters), self);
|
try_smtp!(self.client.upgrade_tls_stream(tls_parameters), self);
|
||||||
|
|
||||||
debug!("connection encrypted");
|
debug!("connection encrypted");
|
||||||
|
|
||||||
// Send EHLO again
|
// Send EHLO again
|
||||||
self.ehlo()?;
|
self.ehlo()?;
|
||||||
}
|
}
|
||||||
|
#[cfg(not(feature = "native-tls"))]
|
||||||
|
(&ClientSecurity::Opportunistic(_), true) | (&ClientSecurity::Required(_), true) => {
|
||||||
|
// FIXME dedicated error variant
|
||||||
|
return Err(From::from("Encryption required but no TLS support enabled"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.client_info.credentials.is_some() {
|
if self.client_info.credentials.is_some() {
|
||||||
|
|||||||
Reference in New Issue
Block a user