diff --git a/lettre/benches/transport_smtp.rs b/lettre/benches/transport_smtp.rs index 33e471e..9191bc0 100644 --- a/lettre/benches/transport_smtp.rs +++ b/lettre/benches/transport_smtp.rs @@ -3,12 +3,15 @@ extern crate lettre; extern crate test; +use lettre::{ClientSecurity, SmtpTransport}; use lettre::{EmailAddress, EmailTransport, SimpleSendableEmail}; -use lettre::smtp::SmtpTransportBuilder; +use lettre::smtp::ConnectionReuseParameters; #[bench] fn bench_simple_send(b: &mut test::Bencher) { - let mut sender = SmtpTransportBuilder::new("127.0.0.1:2525").unwrap().build(); + let mut sender = SmtpTransport::builder("127.0.0.1:2525", ClientSecurity::None) + .unwrap() + .build(); b.iter(|| { let email = SimpleSendableEmail::new( EmailAddress::new("user@localhost".to_string()), @@ -23,9 +26,9 @@ fn bench_simple_send(b: &mut test::Bencher) { #[bench] fn bench_reuse_send(b: &mut test::Bencher) { - let mut sender = SmtpTransportBuilder::new("127.0.0.1:2525") + let mut sender = SmtpTransport::builder("127.0.0.1:2525", ClientSecurity::None) .unwrap() - .connection_reuse(true) + .connection_reuse(ConnectionReuseParameters::ReuseUnlimited) .build(); b.iter(|| { let email = SimpleSendableEmail::new( diff --git a/lettre/examples/smtp.rs b/lettre/examples/smtp.rs index e9bde92..177e738 100644 --- a/lettre/examples/smtp.rs +++ b/lettre/examples/smtp.rs @@ -1,6 +1,6 @@ extern crate lettre; -use lettre::{EmailAddress, EmailTransport, SecurityLevel, SimpleSendableEmail, SmtpTransport}; +use lettre::{EmailAddress, EmailTransport, SimpleSendableEmail, SmtpTransport}; fn main() { let email = SimpleSendableEmail::new( @@ -11,9 +11,8 @@ fn main() { ); // Open a local connection on port 25 - let mut mailer = SmtpTransport::builder_localhost() + let mut mailer = SmtpTransport::builder_unencrypted_localhost() .unwrap() - .security_level(SecurityLevel::Opportunistic) .build(); // Send the email let result = mailer.send(email); diff --git a/lettre/src/file/mod.rs b/lettre/src/file/mod.rs index 498702c..a33937c 100644 --- a/lettre/src/file/mod.rs +++ b/lettre/src/file/mod.rs @@ -74,7 +74,9 @@ impl EmailTransport for FileEmailTransport { email.message(), ); - f.write_all(serde_json::to_string(&simple_email)?.as_bytes())?; + f.write_all( + serde_json::to_string(&simple_email)?.as_bytes(), + )?; Ok(()) } diff --git a/lettre/src/lib.rs b/lettre/src/lib.rs index 1f7be25..5af8d95 100644 --- a/lettre/src/lib.rs +++ b/lettre/src/lib.rs @@ -32,8 +32,9 @@ pub mod file; #[cfg(feature = "file-transport")] pub use file::FileEmailTransport; pub use sendmail::SendmailTransport; -pub use smtp::SecurityLevel; +pub use smtp::ClientSecurity; pub use smtp::SmtpTransport; +pub use smtp::client::net::ClientTlsParameters; use std::fmt; use std::fmt::{Display, Formatter}; diff --git a/lettre/src/sendmail/mod.rs b/lettre/src/sendmail/mod.rs index c95c822..68b0612 100644 --- a/lettre/src/sendmail/mod.rs +++ b/lettre/src/sendmail/mod.rs @@ -33,16 +33,12 @@ pub struct SendmailTransport { impl SendmailTransport { /// Creates a new transport with the default `/usr/sbin/sendmail` command pub fn new() -> SendmailTransport { - SendmailTransport { - command: "/usr/sbin/sendmail".to_string(), - } + SendmailTransport { command: "/usr/sbin/sendmail".to_string() } } /// Creates a new transport to the given sendmail command pub fn new_with_command>(command: S) -> SendmailTransport { - SendmailTransport { - command: command.into(), - } + SendmailTransport { command: command.into() } } } @@ -51,21 +47,21 @@ impl EmailTransport for SendmailTransport { // Spawn the sendmail command let to_addresses: Vec = email.to().iter().map(|x| x.to_string()).collect(); let mut process = Command::new(&self.command) - .args(&[ - "-i", - "-f", - &email.from().to_string(), - &to_addresses.join(" "), - ]) + .args( + &[ + "-i", + "-f", + &email.from().to_string(), + &to_addresses.join(" "), + ], + ) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; - match process - .stdin - .as_mut() - .unwrap() - .write_all(email.message().as_bytes()) { + match process.stdin.as_mut().unwrap().write_all( + email.message().as_bytes(), + ) { Ok(_) => (), Err(error) => return Err(From::from(error)), } diff --git a/lettre/src/smtp/authentication.rs b/lettre/src/smtp/authentication.rs index 44a090d..d6cd57a 100644 --- a/lettre/src/smtp/authentication.rs +++ b/lettre/src/smtp/authentication.rs @@ -213,7 +213,7 @@ mod test { mechanism .response( &credentials, - Some("PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg==") + Some("PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg=="), ) .unwrap(), "alice a540ebe4ef2304070bbc3c456c1f64c0" diff --git a/lettre/src/smtp/client/mod.rs b/lettre/src/smtp/client/mod.rs index d61a726..c121094 100644 --- a/lettre/src/smtp/client/mod.rs +++ b/lettre/src/smtp/client/mod.rs @@ -1,10 +1,9 @@ //! SMTP client use bufstream::BufStream; -use native_tls::TlsConnector; use smtp::{CRLF, MESSAGE_ENDING}; use smtp::authentication::{Credentials, Mechanism}; -use smtp::client::net::{Connector, NetworkStream, Timeout}; +use smtp::client::net::{ClientTlsParameters, Connector, NetworkStream, Timeout}; use smtp::commands::*; use smtp::error::{Error, SmtpResult}; use smtp::response::ResponseParser; @@ -82,9 +81,9 @@ impl Client { } /// Upgrades the underlying connection to SSL/TLS - pub fn upgrade_tls_stream(&mut self, tls_connector: &TlsConnector) -> io::Result<()> { + pub fn upgrade_tls_stream(&mut self, tls_parameters: &ClientTlsParameters) -> io::Result<()> { match self.stream { - Some(ref mut stream) => stream.get_mut().upgrade_tls(tls_connector), + Some(ref mut stream) => stream.get_mut().upgrade_tls(tls_parameters), None => Ok(()), } } @@ -113,7 +112,7 @@ impl Client { pub fn connect( &mut self, addr: &A, - tls_connector: Option<&TlsConnector>, + tls_parameters: Option<&ClientTlsParameters>, ) -> SmtpResult { // Connect should not be called when the client is already connected if self.stream.is_some() { @@ -130,7 +129,7 @@ impl Client { debug!("connecting to {}", server_addr); // Try to connect - self.set_stream(Connector::connect(&server_addr, tls_connector)?); + self.set_stream(Connector::connect(&server_addr, tls_parameters)?); self.get_reply() } @@ -156,8 +155,9 @@ impl Client { // TODO let mut challenges = 10; - let mut response = - self.smtp_command(AuthCommand::new(mechanism, credentials.clone(), None)?)?; + let mut response = self.smtp_command( + AuthCommand::new(mechanism, credentials.clone(), None)?, + )?; while challenges > 0 && response.has_code(334) { challenges -= 1; diff --git a/lettre/src/smtp/client/net.rs b/lettre/src/smtp/client/net.rs index 69238ca..e85ac6f 100644 --- a/lettre/src/smtp/client/net.rs +++ b/lettre/src/smtp/client/net.rs @@ -7,6 +7,25 @@ use std::io::{ErrorKind, Read, Write}; use std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4, TcpStream}; use std::time::Duration; +/// Parameters to use for secure clients +#[derive(Clone)] +pub struct ClientTlsParameters { + /// A connector from `native-tls` + pub connector: TlsConnector, + /// The domain to send during the TLS handshake + pub domain: String, +} + +impl ClientTlsParameters { + /// TODO + pub fn new(domain: String, connector: TlsConnector) -> ClientTlsParameters { + ClientTlsParameters { + connector: connector, + domain: domain, + } + } +} + #[derive(Debug)] /// Represents the different types of underlying network streams pub enum NetworkStream { @@ -73,9 +92,10 @@ impl Write for NetworkStream { /// A trait for the concept of opening a stream pub trait Connector: Sized { /// Opens a connection to the given IP socket - fn connect(addr: &SocketAddr, tls_connector: Option<&TlsConnector>) -> io::Result; + fn connect(addr: &SocketAddr, tls_parameters: Option<&ClientTlsParameters>) + -> io::Result; /// Upgrades to TLS connection - fn upgrade_tls(&mut self, tls_connector: &TlsConnector) -> io::Result<()>; + fn upgrade_tls(&mut self, tls_parameters: &ClientTlsParameters) -> io::Result<()>; /// Is the NetworkStream encrypted fn is_encrypted(&self) -> bool; } @@ -83,14 +103,15 @@ pub trait Connector: Sized { impl Connector for NetworkStream { fn connect( addr: &SocketAddr, - tls_connector: Option<&TlsConnector>, + tls_parameters: Option<&ClientTlsParameters>, ) -> io::Result { let tcp_stream = TcpStream::connect(addr)?; - match tls_connector { + match tls_parameters { Some(context) => { context - .danger_connect_without_providing_domain_for_certificate_verification_and_server_name_indication(tcp_stream) + .connector + .connect(context.domain.as_ref(), tcp_stream) .map(NetworkStream::Tls) .map_err(|e| io::Error::new(ErrorKind::Other, e)) } @@ -99,10 +120,13 @@ impl Connector for NetworkStream { } #[cfg_attr(feature = "cargo-clippy", allow(match_same_arms))] - fn upgrade_tls(&mut self, tls_connector: &TlsConnector) -> io::Result<()> { + fn upgrade_tls(&mut self, tls_parameters: &ClientTlsParameters) -> io::Result<()> { *self = match *self { NetworkStream::Tcp(ref mut stream) => { - match tls_connector.danger_connect_without_providing_domain_for_certificate_verification_and_server_name_indication(stream.try_clone().unwrap()) { + match tls_parameters.connector.connect( + tls_parameters.domain.as_ref(), + stream.try_clone().unwrap(), + ) { Ok(tls_stream) => NetworkStream::Tls(tls_stream), Err(err) => return Err(io::Error::new(ErrorKind::Other, err)), } @@ -112,7 +136,6 @@ impl Connector for NetworkStream { }; Ok(()) - } #[cfg_attr(feature = "cargo-clippy", allow(match_same_arms))] diff --git a/lettre/src/smtp/commands.rs b/lettre/src/smtp/commands.rs index 6e5e917..8fa05a2 100644 --- a/lettre/src/smtp/commands.rs +++ b/lettre/src/smtp/commands.rs @@ -27,9 +27,7 @@ impl Display for EhloCommand { impl EhloCommand { /// Creates a EHLO command pub fn new(client_id: ClientId) -> EhloCommand { - EhloCommand { - client_id: client_id, - } + EhloCommand { client_id: client_id } } } @@ -252,10 +250,10 @@ impl AuthCommand { challenge: Option, ) -> Result { let response = if mechanism.supports_initial_response() || challenge.is_some() { - Some( - mechanism - .response(&credentials, challenge.as_ref().map(String::as_str))?, - ) + Some(mechanism.response( + &credentials, + challenge.as_ref().map(String::as_str), + )?) } else { None }; @@ -297,10 +295,10 @@ impl AuthCommand { debug!("auth decoded challenge: {}", decoded_challenge); - let response = Some( - mechanism - .response(&credentials, Some(decoded_challenge.as_ref()))?, - ); + let response = Some(mechanism.response( + &credentials, + Some(decoded_challenge.as_ref()), + )?); Ok(AuthCommand { mechanism: mechanism, diff --git a/lettre/src/smtp/extension.rs b/lettre/src/smtp/extension.rs index 450ae74..e010508 100644 --- a/lettre/src/smtp/extension.rs +++ b/lettre/src/smtp/extension.rs @@ -153,8 +153,9 @@ impl ServerInfo { /// Checks if the server supports an ESMTP feature pub fn supports_auth_mechanism(&self, mechanism: Mechanism) -> bool { - self.features - .contains(&Extension::Authentication(mechanism)) + self.features.contains( + &Extension::Authentication(mechanism), + ) } } @@ -349,9 +350,13 @@ mod test { let mut features2 = HashSet::new(); assert!(features2.insert(Extension::EightBitMime)); - assert!(features2.insert(Extension::Authentication(Mechanism::Plain),)); + assert!(features2.insert( + Extension::Authentication(Mechanism::Plain), + )); #[cfg(feature = "crammd5-auth")] - assert!(features2.insert(Extension::Authentication(Mechanism::CramMd5),)); + assert!(features2.insert( + Extension::Authentication(Mechanism::CramMd5), + )); let server_info2 = ServerInfo { name: "me".to_string(), diff --git a/lettre/src/smtp/mod.rs b/lettre/src/smtp/mod.rs index e174c01..7de3aa8 100644 --- a/lettre/src/smtp/mod.rs +++ b/lettre/src/smtp/mod.rs @@ -18,7 +18,7 @@ //! This is the most basic example of usage: //! //! ```rust,no_run -//! use lettre::{SimpleSendableEmail, EmailTransport, EmailAddress, SmtpTransport, SecurityLevel}; +//! use lettre::{SimpleSendableEmail, EmailTransport, EmailAddress, SmtpTransport}; //! //! let email = SimpleSendableEmail::new( //! EmailAddress::new("user@localhost".to_string()), @@ -29,8 +29,7 @@ //! //! // Open a local connection on port 25 //! let mut mailer = -//! SmtpTransport::builder_localhost().unwrap() -//! .security_level(SecurityLevel::Opportunistic).build(); +//! SmtpTransport::builder_unencrypted_localhost().unwrap().build(); //! // Send the email //! let result = mailer.send(email); //! @@ -42,8 +41,10 @@ //! ```rust,no_run //! use lettre::smtp::authentication::{Credentials, Mechanism}; //! use lettre::smtp::SUBMISSION_PORT; -//! use lettre::{SimpleSendableEmail, EmailTransport, EmailAddress, SmtpTransport, SecurityLevel}; +//! use lettre::{SimpleSendableEmail, EmailTransport, EmailAddress, SmtpTransport}; //! use lettre::smtp::extension::ClientId; +//! use lettre::smtp::ConnectionReuseParameters; +//! //! //! let email = SimpleSendableEmail::new( //! EmailAddress::new("user@localhost".to_string()), @@ -53,23 +54,18 @@ //! ); //! //! // Connect to a remote server on a custom port -//! let mut mailer = SmtpTransport::builder(("server.tld", -//! SUBMISSION_PORT)).unwrap() +//! let mut mailer = SmtpTransport::simple_builder("server.tld".to_string()).unwrap() //! // Set the name sent during EHLO/HELO, default is `localhost` //! .hello_name(ClientId::Domain("my.hostname.tld".to_string())) //! // Add credentials for authentication //! .credentials(Credentials::new("username".to_string(), "password".to_string())) -//! // Specify a TLS security level. You can also specify an TlsConnector with -//! // .tls_connector(TlsConnector::builder().unwrap() -//! // .supported_protocols(&vec![Protocol::Tlsv12]) -//! // .build().unwrap()) -//! .security_level(SecurityLevel::AlwaysEncrypt) +//! // FIXME security doc //! // Enable SMTPUTF8 if the server supports it //! .smtp_utf8(true) //! // Configure expected authentication mechanism //! .authentication_mechanism(Mechanism::Plain) //! // Enable connection reuse -//! .connection_reuse(true).build(); +//! .connection_reuse(ConnectionReuseParameters::ReuseUnlimited).build(); //! //! let result_1 = mailer.send(email.clone()); //! assert!(result_1.is_ok()); @@ -116,6 +112,7 @@ use native_tls::TlsConnector; use smtp::authentication::{Credentials, DEFAULT_ENCRYPTED_MECHANISMS, DEFAULT_UNENCRYPTED_MECHANISMS, Mechanism}; use smtp::client::Client; +use smtp::client::net::ClientTlsParameters; use smtp::commands::*; use smtp::error::{Error, SmtpResult}; use smtp::extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo}; @@ -157,43 +154,43 @@ pub const MESSAGE_ENDING: &'static str = "\r\n.\r\n"; /// NUL unicode character pub const NUL: &'static str = "\0"; -/// TLS security level -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -pub enum SecurityLevel { - /// Use a TLS wrapped connection - /// +/// How to apply TLS to a client connection +#[derive(Clone)] +pub enum ClientSecurity { + /// Insecure connection + None, + /// Use `STARTTLS` when available + Opportunistic(ClientTlsParameters), + /// Always use `STARTTLS` + Required(ClientTlsParameters), + /// Use TLS wrapped connection without negotation /// Non RFC-compliant, should only be used if the server does not support STARTTLS. - EncryptedWrapper, - /// Only send an email on encrypted connection (with STARTTLS) - /// - /// Default mode, prevents MITM when used with verified certificates. - AlwaysEncrypt, - /// Use TLS when available (with STARTTLS) - /// - /// Should be used when not possible to always encrypt the connection - Opportunistic, - /// Never use encryption - NeverEncrypt, + Wrapper(ClientTlsParameters), +} + +/// Configures connection reuse behavior +#[derive(Clone, Debug)] +pub enum ConnectionReuseParameters { + /// Unlimitied connection reuse + ReuseUnlimited, + /// Maximum number of connection reuse + ReuseLimited(u16), + /// Disable connection reuse, close connection after each transaction + NoReuse, } /// Contains client configuration pub struct SmtpTransportBuilder { - /// Maximum connection reuse - /// - /// Zero means no limitation - connection_reuse_count_limit: u16, /// Enable connection reuse - connection_reuse: bool, - /// Name sent during HELO or EHLO + connection_reuse: ConnectionReuseParameters, + /// Name sent during EHLO hello_name: ClientId, /// Credentials credentials: Option, /// Socket we are connecting to server_addr: SocketAddr, - /// SSL context to use - tls_connector: TlsConnector, - /// TLS security level - security_level: SecurityLevel, + /// TLS security configuration + security: ClientSecurity, /// Enable UTF8 mailboxes in envelope or headers smtp_utf8: bool, /// Optional enforced authentication mechanism @@ -205,20 +202,29 @@ pub struct SmtpTransportBuilder { /// Builder for the SMTP `SmtpTransport` impl SmtpTransportBuilder { - /// Creates a new local SMTP client - pub fn new(addr: A) -> Result { + /// Creates a new SMTP client + /// + /// Defaults are: + /// + /// * No connection reuse + /// * "localhost" as EHLO name + /// * No authentication + /// * No SMTPUTF8 support + /// * A 60 seconds timeout for smtp commands + pub fn new( + addr: A, + security: ClientSecurity, + ) -> Result { let mut addresses = addr.to_socket_addrs()?; match addresses.next() { Some(addr) => { Ok(SmtpTransportBuilder { server_addr: addr, - tls_connector: TlsConnector::builder().unwrap().build().unwrap(), - security_level: SecurityLevel::AlwaysEncrypt, + security: security, smtp_utf8: false, credentials: None, - connection_reuse_count_limit: 100, - connection_reuse: false, + connection_reuse: ConnectionReuseParameters::NoReuse, hello_name: ClientId::Domain("localhost".to_string()), authentication_mechanism: None, timeout: Some(Duration::new(60, 0)), @@ -228,55 +234,24 @@ impl SmtpTransportBuilder { } } - /// Use STARTTLS with a specific context - pub fn tls_connector(mut self, tls_context: TlsConnector) -> SmtpTransportBuilder { - self.tls_connector = tls_context; - self - } - - /// Set the security level for TLS - pub fn security_level(mut self, level: SecurityLevel) -> SmtpTransportBuilder { - self.security_level = level; - self - } - - /// Require TLS using STARTTLS - /// - /// Incompatible with `tls_wrapper()`` - pub fn encrypt(mut self) -> SmtpTransportBuilder { - self.security_level = SecurityLevel::AlwaysEncrypt; - self - } - - /// Require TLS using SMTPS - /// - /// Incompatible with `encrypt()` - pub fn tls_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 } - /// Set the name used during HELO or EHLO + /// Set the name used during EHLO pub fn hello_name(mut self, name: ClientId) -> SmtpTransportBuilder { self.hello_name = name; self } /// Enable connection reuse - pub fn connection_reuse(mut self, enable: bool) -> SmtpTransportBuilder { - self.connection_reuse = enable; - self - } - - /// Set the maximum number of emails sent using one connection - pub fn connection_reuse_count_limit(mut self, limit: u16) -> SmtpTransportBuilder { - self.connection_reuse_count_limit = limit; + pub fn connection_reuse( + mut self, + parameters: ConnectionReuseParameters, + ) -> SmtpTransportBuilder { + self.connection_reuse = parameters; self } @@ -286,7 +261,7 @@ impl SmtpTransportBuilder { self } - /// Set the authentication mechanisms + /// Set the authentication mechanism to use pub fn authentication_mechanism(mut self, mechanism: Mechanism) -> SmtpTransportBuilder { self.authentication_mechanism = Some(mechanism); self @@ -344,13 +319,32 @@ macro_rules! try_smtp ( ); impl SmtpTransport { - /// TODO - pub fn builder(addr: A) -> Result { - SmtpTransportBuilder::new(addr) + /// Simple and secure transport, should be used when possible. + /// Creates an encrypted transport over submission port, using the provided domain + /// to validate TLS certificates. + pub fn simple_builder(domain: String) -> Result { + let tls_parameters = ClientTlsParameters::new( + domain.clone(), + TlsConnector::builder().unwrap().build().unwrap(), + ); + + SmtpTransportBuilder::new( + (domain.as_ref(), SUBMISSION_PORT), + ClientSecurity::Required(tls_parameters), + ) } + + /// Creates a new configurable builder + pub fn builder( + addr: A, + security: ClientSecurity, + ) -> Result { + SmtpTransportBuilder::new(addr, security) + } + /// Creates a new local SMTP client to port 25 - pub fn builder_localhost() -> Result { - SmtpTransportBuilder::new(("localhost", SMTP_PORT)) + pub fn builder_unencrypted_localhost() -> Result { + SmtpTransportBuilder::new(("localhost", SMTP_PORT), ClientSecurity::None) } /// Creates a new SMTP client @@ -388,7 +382,7 @@ impl SmtpTransport { let ehlo_response = try_smtp!( self.client.smtp_command(EhloCommand::new( ClientId::new(self.client_info.hello_name.to_string()), - ),), + )), self ); @@ -417,8 +411,8 @@ impl EmailTransport for SmtpTransport { if self.state.connection_reuse_count == 0 { self.client.connect( &self.client_info.server_addr, - match self.client_info.security_level { - SecurityLevel::EncryptedWrapper => Some(&self.client_info.tls_connector), + match self.client_info.security { + ClientSecurity::Wrapper(ref tls_parameters) => Some(tls_parameters), _ => None, }, )?; @@ -431,25 +425,21 @@ impl EmailTransport for SmtpTransport { self.get_ehlo()?; match ( - &self.client_info.security_level, - self.server_info - .as_ref() - .unwrap() - .supports_feature(Extension::StartTls), + &self.client_info.security.clone(), + self.server_info.as_ref().unwrap().supports_feature( + Extension::StartTls, + ), ) { - (&SecurityLevel::AlwaysEncrypt, false) => { + (&ClientSecurity::Required(_), false) => { return Err(From::from("Could not encrypt connection, aborting")) } - (&SecurityLevel::Opportunistic, false) => (), - (&SecurityLevel::NeverEncrypt, _) => (), - (&SecurityLevel::EncryptedWrapper, _) => (), - (_, true) => { + (&ClientSecurity::Opportunistic(_), false) => (), + (&ClientSecurity::None, _) => (), + (&ClientSecurity::Wrapper(_), _) => (), + (&ClientSecurity::Opportunistic(ref tls_parameters), true) | + (&ClientSecurity::Required(ref tls_parameters), true) => { try_smtp!(self.client.smtp_command(StarttlsCommand), self); - try_smtp!( - self.client - .upgrade_tls_stream(&self.client_info.tls_connector,), - self - ); + try_smtp!(self.client.upgrade_tls_stream(tls_parameters), self); debug!("connection encrypted"); @@ -474,15 +464,16 @@ impl EmailTransport for SmtpTransport { }; for mechanism in accepted_mechanisms { - if self.server_info - .as_ref() - .unwrap() - .supports_auth_mechanism(mechanism) + if self.server_info.as_ref().unwrap().supports_auth_mechanism( + mechanism, + ) { found = true; try_smtp!( - self.client - .auth(mechanism, self.client_info.credentials.as_ref().unwrap(),), + self.client.auth( + mechanism, + self.client_info.credentials.as_ref().unwrap(), + ), self ); break; @@ -498,25 +489,25 @@ impl EmailTransport for SmtpTransport { // Mail let mut mail_options = vec![]; - if self.server_info - .as_ref() - .unwrap() - .supports_feature(Extension::EightBitMime) + if self.server_info.as_ref().unwrap().supports_feature( + Extension::EightBitMime, + ) { mail_options.push(MailParameter::Body(MailBodyParameter::EightBitMime)); } - if self.server_info - .as_ref() - .unwrap() - .supports_feature(Extension::SmtpUtfEight) + if self.server_info.as_ref().unwrap().supports_feature( + Extension::SmtpUtfEight, + ) && self.client_info.smtp_utf8 { mail_options.push(MailParameter::SmtpUtfEight); } try_smtp!( - self.client - .smtp_command(MailCommand::new(Some(email.from().clone()), mail_options,),), + self.client.smtp_command(MailCommand::new( + Some(email.from().clone()), + mail_options, + )), self ); @@ -526,8 +517,9 @@ impl EmailTransport for SmtpTransport { // Recipient for to_address in &email.to() { try_smtp!( - self.client - .smtp_command(RcptCommand::new(to_address.clone(), vec![]),), + self.client.smtp_command( + RcptCommand::new(to_address.clone(), vec![]), + ), self ); // Log the rcpt command @@ -563,10 +555,11 @@ impl EmailTransport for SmtpTransport { } // Test if we can reuse the existing connection - if (!self.client_info.connection_reuse) || - (self.state.connection_reuse_count >= self.client_info.connection_reuse_count_limit) - { - self.reset(); + match self.client_info.connection_reuse { + ConnectionReuseParameters::ReuseLimited(limit) + if self.state.connection_reuse_count >= limit => self.reset(), + ConnectionReuseParameters::NoReuse => self.reset(), + _ => (), } result diff --git a/lettre/src/smtp/response.rs b/lettre/src/smtp/response.rs index e999eb0..d1d321b 100644 --- a/lettre/src/smtp/response.rs +++ b/lettre/src/smtp/response.rs @@ -274,9 +274,9 @@ impl Response { /// Returns only the first word of the message if possible pub fn first_word(&self) -> Option<&str> { - self.message - .get(0) - .and_then(|line| line.split_whitespace().next()) + self.message.get(0).and_then( + |line| line.split_whitespace().next(), + ) } /// Returns only the line of the message if possible diff --git a/lettre/src/smtp/util.rs b/lettre/src/smtp/util.rs index 7afc111..0c08c0b 100644 --- a/lettre/src/smtp/util.rs +++ b/lettre/src/smtp/util.rs @@ -39,7 +39,8 @@ mod tests { ("bjørn", "bjørn"), ("Ø+= ❤️‰", "Ø+2B+3D+20❤️‰"), ("+", "+2B"), - ] { + ] + { assert_eq!(format!("{}", XText(input)), expect); } } diff --git a/lettre/tests/transport_smtp.rs b/lettre/tests/transport_smtp.rs index ba52b84..bf02c18 100644 --- a/lettre/tests/transport_smtp.rs +++ b/lettre/tests/transport_smtp.rs @@ -1,14 +1,11 @@ extern crate lettre; -use lettre::{EmailAddress, EmailTransport, SimpleSendableEmail}; -use lettre::smtp::SecurityLevel; -use lettre::smtp::SmtpTransportBuilder; +use lettre::{ClientSecurity, EmailAddress, EmailTransport, SimpleSendableEmail, SmtpTransport}; #[test] fn smtp_transport_simple() { - let mut sender = SmtpTransportBuilder::new("127.0.0.1:2525") + let mut sender = SmtpTransport::builder("127.0.0.1:2525", ClientSecurity::None) .unwrap() - .security_level(SecurityLevel::Opportunistic) .build(); let email = SimpleSendableEmail::new( EmailAddress::new("user@localhost".to_string()), diff --git a/lettre_email/examples/smtp.rs b/lettre_email/examples/smtp.rs index 2247e92..00e99bb 100644 --- a/lettre_email/examples/smtp.rs +++ b/lettre_email/examples/smtp.rs @@ -16,7 +16,9 @@ fn main() { .unwrap(); // Open a local connection on port 25 - let mut mailer = SmtpTransport::builder_localhost().unwrap().build(); + let mut mailer = SmtpTransport::builder_unencrypted_localhost() + .unwrap() + .build(); // Send the email let result = mailer.send(email); diff --git a/lettre_email/src/lib.rs b/lettre_email/src/lib.rs index 4d5d19c..386303f 100644 --- a/lettre_email/src/lib.rs +++ b/lettre_email/src/lib.rs @@ -391,9 +391,7 @@ impl Display for Email { impl PartBuilder { /// Creates a new empty part pub fn new() -> PartBuilder { - PartBuilder { - message: MimeMessage::new_blank_message(), - } + PartBuilder { message: MimeMessage::new_blank_message() } } /// Adds a generic header @@ -576,8 +574,9 @@ impl EmailBuilder { /// Adds a `Subject` header pub fn set_subject>(&mut self, subject: S) { - self.message - .add_header(("Subject".to_string(), subject.into())); + self.message.add_header( + ("Subject".to_string(), subject.into()), + ); } /// Adds a `Date` header with the given date @@ -588,8 +587,9 @@ impl EmailBuilder { /// Adds a `Date` header with the given date pub fn set_date(&mut self, date: &Tm) { - self.message - .add_header(("Date", Tm::rfc822z(date).to_string())); + self.message.add_header( + ("Date", Tm::rfc822z(date).to_string()), + ); self.date_issued = true; } @@ -639,8 +639,10 @@ impl EmailBuilder { /// Sets the email body to HTML content pub fn set_html>(&mut self, body: S) { self.message.set_body(body); - self.message - .add_header(("Content-Type", format!("{}", mime::TEXT_HTML).as_ref())); + self.message.add_header(( + "Content-Type", + format!("{}", mime::TEXT_HTML).as_ref(), + )); } /// Sets the email content @@ -725,10 +727,9 @@ impl EmailBuilder { // we need to generate the envelope let mut e = Envelope::new(); // add all receivers in to_header and cc_header - for receiver in self.to_header - .iter() - .chain(self.cc_header.iter()) - .chain(self.bcc_header.iter()) + for receiver in self.to_header.iter().chain(self.cc_header.iter()).chain( + self.bcc_header.iter(), + ) { match *receiver { Address::Mailbox(ref m) => e.add_to(m.address.clone()), @@ -769,8 +770,12 @@ impl EmailBuilder { // Add the collected addresses as mailbox-list all at once. // The unwraps are fine because the conversions for Vec
never errs. if !self.to_header.is_empty() { - self.message - .add_header(Header::new_with_value("To".into(), self.to_header).unwrap()); + self.message.add_header( + Header::new_with_value( + "To".into(), + self.to_header, + ).unwrap(), + ); } if !self.from_header.is_empty() { self.message.add_header( @@ -780,8 +785,12 @@ impl EmailBuilder { return Err(Error::MissingFrom); } if !self.cc_header.is_empty() { - self.message - .add_header(Header::new_with_value("Cc".into(), self.cc_header).unwrap()); + self.message.add_header( + Header::new_with_value( + "Cc".into(), + self.cc_header, + ).unwrap(), + ); } if !self.reply_to_header.is_empty() { self.message.add_header( @@ -790,8 +799,10 @@ impl EmailBuilder { } if !self.date_issued { - self.message - .add_header(("Date", Tm::rfc822z(&now()).to_string().as_ref())); + self.message.add_header(( + "Date", + Tm::rfc822z(&now()).to_string().as_ref(), + )); } self.message.add_header(("MIME-Version", "1.0")); @@ -801,7 +812,8 @@ impl EmailBuilder { if let Ok(header) = Header::new_with_value( "Message-ID".to_string(), format!("<{}.lettre@localhost>", message_id), - ) { + ) + { self.message.add_header(header) } @@ -922,7 +934,8 @@ mod test { ); email.message.headers.insert( - Header::new_with_value("To".to_string(), "to@example.com".to_string()).unwrap(), + Header::new_with_value("To".to_string(), "to@example.com".to_string()) + .unwrap(), ); email.message.body = "body".to_string();