From a91db148694eb690e49b2919e1ec546a7a6a3ee1 Mon Sep 17 00:00:00 2001 From: Alexis Mousset Date: Mon, 12 Oct 2015 22:47:53 +0200 Subject: [PATCH] Configurable TLS security level --- Cargo.toml | 2 +- src/lib.rs | 8 ++++---- src/sender.rs | 50 +++++++++++++++++++++++++++++++++++++------------- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7cb61f9..4e2032a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "smtp" -version = "0.3.0" +version = "0.4.0" description = "Simple SMTP client" readme = "README.md" documentation = "http://amousset.me/rust-smtp/smtp/" diff --git a/src/lib.rs b/src/lib.rs index 02a2a40..af585d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -54,7 +54,7 @@ //! ### Complete example //! //! ```rust,no_run -//! use smtp::sender::{Sender, SenderBuilder}; +//! use smtp::sender::{SecurityLevel, Sender, SenderBuilder}; //! use smtp::email::EmailBuilder; //! use smtp::authentication::Mecanism; //! use smtp::SUBMISSION_PORT; @@ -78,9 +78,9 @@ //! .hello_name("my.hostname.tld") //! // Add credentials for authentication //! .credentials("username", "password") -//! // Use TLS with STARTTLS, you can also specify a specific SSL context -//! // with `.ssl_context(context)` -//! .starttls() +//! // Specify a TLS security level. You can also specify an SslContext with +//! // .ssl_context(SslContext::Ssl23) +//! .security_level(SecurityLevel::AlwaysEncrypt) //! // Configure accepted authetication mecanisms //! .authentication_mecanisms(vec![Mecanism::CramMd5]) //! // Enable connection reuse diff --git a/src/sender.rs b/src/sender.rs index cbf4e5c..a3f4256 100644 --- a/src/sender.rs +++ b/src/sender.rs @@ -12,6 +12,16 @@ use email::SendableEmail; use client::Client; use authentication::Mecanism; +/// TLS security level +pub enum SecurityLevel { + /// Only send an email on encrypted connection + AlwaysEncrypt, + /// Use TLS when available + Opportunistic, + /// Never use TLS + NeverEncrypt, +} + /// Contains client configuration pub struct SenderBuilder { /// Maximum connection reuse @@ -27,7 +37,9 @@ pub struct SenderBuilder { /// Socket we are connecting to server_addr: SocketAddr, /// SSL contexyt to use - ssl_context: Option, + ssl_context: SslContext, + /// TLS security level + security_level: SecurityLevel, /// List of authentication mecanism, sorted by priority authentication_mecanisms: Vec, } @@ -41,7 +53,8 @@ impl SenderBuilder { match addresses.next() { Some(addr) => Ok(SenderBuilder { server_addr: addr, - ssl_context: None, + ssl_context: SslContext::new(SslMethod::Tlsv1).unwrap(), + security_level: SecurityLevel::Opportunistic, credentials: None, connection_reuse_count_limit: 100, enable_connection_reuse: false, @@ -59,13 +72,14 @@ impl SenderBuilder { /// Use STARTTLS with a specific context pub fn ssl_context(mut self, ssl_context: SslContext) -> SenderBuilder { - self.ssl_context = Some(ssl_context); + self.ssl_context = ssl_context; self } /// Require SSL/TLS using STARTTLS - pub fn starttls(self) -> SenderBuilder { - self.ssl_context(SslContext::new(SslMethod::Tlsv1).unwrap()) + pub fn security_level(mut self, level: SecurityLevel) -> SenderBuilder { + self.security_level = level; + self } /// Set the name used during HELO or EHLO @@ -137,7 +151,7 @@ macro_rules! try_smtp ( $client.state.panic = true; $client.reset(); } - return Err(err) + return Err(From::from(err)) }, } }) @@ -209,16 +223,26 @@ impl Sender { try!(self.get_ehlo()); - if self.client_info.ssl_context.is_some() { - try_smtp!(self.client.starttls(), self); + match (&self.client_info.security_level, + self.server_info.as_ref().unwrap().supports_feature(&Extension::StartTls)) { + (&SecurityLevel::AlwaysEncrypt, false) => + return Err(From::from("Could not encrypt connection, aborting")), + (&SecurityLevel::Opportunistic, false) => (), + (&SecurityLevel::NeverEncrypt, _) => (), + (_, true) => { + try_smtp!(self.client.starttls(), self); + try_smtp!(self.client.upgrade_tls_stream(&self.client_info.ssl_context), + self); - try!(self.client - .upgrade_tls_stream(self.client_info.ssl_context.as_ref().unwrap())); + debug!("connection encrypted"); + + // Send EHLO again + try!(self.get_ehlo()); + } - try!(self.get_ehlo()); } - if self.client_info.credentials.is_some() && self.state.connection_reuse_count == 0 { + if self.client_info.credentials.is_some() { let (username, password) = self.client_info.credentials.clone().unwrap(); let mut found = false; @@ -232,7 +256,7 @@ impl Sender { } if !found { - debug!("No supported authentication mecanisms available"); + info!("No supported authentication mecanisms available"); } } }