Add SMTPS support

This commit is contained in:
Alexis Mousset
2015-10-27 22:38:45 +01:00
parent 48eb859804
commit 9f177047f8
4 changed files with 50 additions and 16 deletions

View File

@@ -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");

View File

@@ -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());

View File

@@ -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))
}

View File

@@ -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),