Add SMTPS support
This commit is contained in:
@@ -138,7 +138,7 @@
|
||||
//! use lettre::transport::smtp::client::net::NetworkStream;
|
||||
//!
|
||||
//! let mut email_client: Client<NetworkStream> = Client::new();
|
||||
//! let _ = email_client.connect(&("localhost", SMTP_PORT));
|
||||
//! let _ = email_client.connect(&("localhost", SMTP_PORT), None);
|
||||
//! let _ = email_client.ehlo("my_hostname");
|
||||
//! let _ = email_client.mail("user@example.com", None);
|
||||
//! let _ = email_client.rcpt("user@example.org");
|
||||
|
||||
@@ -53,7 +53,8 @@ impl Mechanism {
|
||||
match *self {
|
||||
Mechanism::Plain => {
|
||||
match challenge {
|
||||
Some(_) => Err(Error::ClientError("This mechanism does not expect a challenge")),
|
||||
Some(_) =>
|
||||
Err(Error::ClientError("This mechanism does not expect a challenge")),
|
||||
None => Ok(format!("{}{}{}{}", NUL, username, NUL, password)
|
||||
.as_bytes()
|
||||
.to_base64(base64::STANDARD)),
|
||||
@@ -62,7 +63,8 @@ impl Mechanism {
|
||||
Mechanism::CramMd5 => {
|
||||
let encoded_challenge = match challenge {
|
||||
Some(challenge) => challenge,
|
||||
None => return Err(Error::ClientError("This mechanism does expect a challenge")),
|
||||
None =>
|
||||
return Err(Error::ClientError("This mechanism does expect a challenge")),
|
||||
};
|
||||
|
||||
let decoded_challenge = match encoded_challenge.from_base64() {
|
||||
@@ -99,9 +101,10 @@ mod test {
|
||||
let mechanism = Mechanism::CramMd5;
|
||||
|
||||
assert_eq!(mechanism.response("alice",
|
||||
"wonderland",
|
||||
Some("PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg=="))
|
||||
.unwrap(),
|
||||
"wonderland",
|
||||
Some("PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg=\
|
||||
="))
|
||||
.unwrap(),
|
||||
"YWxpY2UgNjRiMmE0M2MxZjZlZDY4MDZhOTgwOTE0ZTIzZTc1ZjA=");
|
||||
assert!(mechanism.response("alice", "wonderland", Some("tést")).is_err());
|
||||
assert!(mechanism.response("alice", "wonderland", None).is_err());
|
||||
|
||||
@@ -88,7 +88,10 @@ impl<S: Connector + Write + Read + Debug + Clone = NetworkStream> Client<S> {
|
||||
}
|
||||
|
||||
/// Connects to the configured server
|
||||
pub fn connect<A: ToSocketAddrs>(&mut self, addr: &A) -> EmailResult {
|
||||
pub fn connect<A: ToSocketAddrs>(&mut self,
|
||||
addr: &A,
|
||||
ssl_context: Option<&SslContext>)
|
||||
-> EmailResult {
|
||||
// Connect should not be called when the client is already connected
|
||||
if self.stream.is_some() {
|
||||
return_err!("The connection is already established", self);
|
||||
@@ -102,7 +105,7 @@ impl<S: Connector + Write + Read + Debug + Clone = NetworkStream> Client<S> {
|
||||
};
|
||||
|
||||
// Try to connect
|
||||
self.set_stream(try!(Connector::connect(&server_addr, None)));
|
||||
self.set_stream(try!(Connector::connect(&server_addr, ssl_context)));
|
||||
|
||||
self.get_reply()
|
||||
}
|
||||
@@ -189,8 +192,8 @@ impl<S: Connector + Write + Read + Debug + Clone = NetworkStream> Client<S> {
|
||||
debug!("CRAM challenge: {}", encoded_challenge);
|
||||
|
||||
let cram_response = try!(mechanism.response(username,
|
||||
password,
|
||||
Some(&encoded_challenge)));
|
||||
password,
|
||||
Some(&encoded_challenge)));
|
||||
|
||||
self.command(&format!("{}", cram_response))
|
||||
}
|
||||
|
||||
@@ -47,9 +47,17 @@ pub static NUL: &'static str = "\0";
|
||||
/// TLS security level
|
||||
#[derive(Debug)]
|
||||
pub enum SecurityLevel {
|
||||
/// Only send an email on encrypted connection
|
||||
/// Use a TLS wrapped connection
|
||||
///
|
||||
/// Non RFC-compliant, should only be used if the server does not support STARTTLS.
|
||||
EncryptedWrapper,
|
||||
/// Only send an email on encrypted connection (with STARTTLS)
|
||||
///
|
||||
/// Recommended mode, prevents MITM when used with verified certificates.
|
||||
AlwaysEncrypt,
|
||||
/// Use TLS when available
|
||||
/// Use TLS when available (with STARTTLS)
|
||||
///
|
||||
/// Default mode.
|
||||
Opportunistic,
|
||||
/// Never use TLS
|
||||
NeverEncrypt,
|
||||
@@ -112,13 +120,29 @@ impl SmtpTransportBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Require SSL/TLS using STARTTLS
|
||||
/// Set the security level for SSL/TLS
|
||||
pub fn security_level(mut self, level: SecurityLevel) -> SmtpTransportBuilder {
|
||||
self.security_level = level;
|
||||
self
|
||||
}
|
||||
|
||||
/// Require SSL/TLS using STARTTLS
|
||||
///
|
||||
/// Incompatible with `ssl_wrapper()``
|
||||
pub fn encrypt(mut self) -> SmtpTransportBuilder {
|
||||
self.security_level = SecurityLevel::AlwaysEncrypt;
|
||||
self
|
||||
}
|
||||
|
||||
/// Require SSL/TLS using STARTTLS
|
||||
///
|
||||
/// Incompatible with `encrypt()`
|
||||
pub fn ssl_wrapper(mut self) -> SmtpTransportBuilder {
|
||||
self.security_level = SecurityLevel::EncryptedWrapper;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable SMTPUTF8 if the server supports it
|
||||
pub fn smtp_utf8(mut self, enabled: bool) -> SmtpTransportBuilder {
|
||||
self.smtp_utf8 = enabled;
|
||||
self
|
||||
@@ -260,10 +284,13 @@ impl EmailTransport for SmtpTransport {
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a usable connection, test if the server answers and hello has
|
||||
// been sent
|
||||
if self.state.connection_reuse_count == 0 {
|
||||
try!(self.client.connect(&self.client_info.server_addr));
|
||||
try!(self.client.connect(&self.client_info.server_addr,
|
||||
match &self.client_info.security_level {
|
||||
&SecurityLevel::EncryptedWrapper =>
|
||||
Some(&self.client_info.ssl_context),
|
||||
_ => None,
|
||||
}));
|
||||
|
||||
// Log the connection
|
||||
info!("connection established to {}", self.client_info.server_addr);
|
||||
@@ -276,6 +303,7 @@ impl EmailTransport for SmtpTransport {
|
||||
return Err(From::from("Could not encrypt connection, aborting")),
|
||||
(&SecurityLevel::Opportunistic, false) => (),
|
||||
(&SecurityLevel::NeverEncrypt, _) => (),
|
||||
(&SecurityLevel::EncryptedWrapper, _) => (),
|
||||
(_, true) => {
|
||||
try_smtp!(self.client.starttls(), self);
|
||||
try_smtp!(self.client.upgrade_tls_stream(&self.client_info.ssl_context),
|
||||
|
||||
Reference in New Issue
Block a user