Add STARTTLS support instead of SMTPS

This commit is contained in:
Alexis Mousset
2015-10-12 00:59:39 +02:00
parent 51de392086
commit 47d6870d93
4 changed files with 85 additions and 51 deletions

View File

@@ -3,6 +3,8 @@
use std::string::String;
use std::net::ToSocketAddrs;
use std::io::{BufRead, Read, Write};
use std::io;
use std::fmt::Debug;
use bufstream::BufStream;
use openssl::ssl::SslContext;
@@ -65,15 +67,30 @@ impl<S: Write + Read = NetworkStream> Client<S> {
}
}
impl<S: Connector + Write + Read = NetworkStream> Client<S> {
impl<S: Connector + Write + Read + Debug + Clone = NetworkStream> Client<S> {
/// Closes the SMTP transaction if possible
pub fn close(&mut self) {
let _ = self.quit();
self.stream = None;
}
/// Sets the underlying stream
pub fn set_stream(&mut self, stream: S) {
self.stream = Some(BufStream::new(stream));
}
/// Upgrades the underlying connection to SSL/TLS
pub fn upgrade_tls_stream(&mut self, ssl_context: &SslContext) -> io::Result<()> {
//let current_stream = self.stream.clone();
if self.stream.is_some() {
self.stream.as_mut().unwrap().get_mut().upgrade_tls(ssl_context)
} else {
Ok(())
}
}
/// Connects to the configured server
pub fn connect<A: ToSocketAddrs>(&mut self, addr: &A, ssl_context: Option<&SslContext>) -> SmtpResult {
pub fn connect<A: ToSocketAddrs>(&mut self, addr: &A) -> SmtpResult {
// Connect should not be called when the client is already connected
if self.stream.is_some() {
return_err!("The connection is already established", self);
@@ -87,7 +104,7 @@ impl<S: Connector + Write + Read = NetworkStream> Client<S> {
};
// Try to connect
self.stream = Some(BufStream::new(try!(Connector::connect(&server_addr, ssl_context))));
self.set_stream(try!(Connector::connect(&server_addr, None)));
self.get_reply()
}
@@ -102,12 +119,7 @@ impl<S: Connector + Write + Read = NetworkStream> Client<S> {
self.send_server(command, CRLF)
}
/// Send a HELO command and fills `server_info`
pub fn helo(&mut self, hostname: &str) -> SmtpResult {
self.command(&format!("HELO {}", hostname))
}
/// Sends a EHLO command and fills `server_info`
/// Sends a EHLO command
pub fn ehlo(&mut self, hostname: &str) -> SmtpResult {
self.command(&format!("EHLO {}", hostname))
}
@@ -186,6 +198,11 @@ impl<S: Connector + Write + Read = NetworkStream> Client<S> {
}
}
/// Sends a STARTTLS command
pub fn starttls(&mut self) -> SmtpResult {
self.command("STARTTLS")
}
/// Sends the message content
pub fn message(&mut self, message_content: &str) -> SmtpResult {
self.send_server(&escape_dot(message_content), MESSAGE_ENDING)

View File

@@ -12,6 +12,8 @@ use openssl::ssl::{SslContext, SslStream};
pub trait Connector {
/// Opens a connection to the given IP socket
fn connect(addr: &SocketAddr, ssl_context: Option<&SslContext>) -> io::Result<Self>;
/// Upgrades to TLS connection
fn upgrade_tls(&mut self, ssl_context: &SslContext) -> io::Result<()>;
}
impl Connector for NetworkStream {
@@ -26,8 +28,20 @@ impl Connector for NetworkStream {
None => Ok(NetworkStream::Plain(tcp_stream)),
}
}
fn upgrade_tls(&mut self, ssl_context: &SslContext) -> io::Result<()> {
*self = match self.clone() {
NetworkStream::Plain(stream) => match SslStream::new(ssl_context, stream) {
Ok(ssl_stream) => NetworkStream::Ssl(ssl_stream),
Err(err) => return Err(io::Error::new(ErrorKind::Other, err)),
},
NetworkStream::Ssl(stream) => NetworkStream::Ssl(stream),
};
Ok(())
}
}
/// Represents the different types of underlying network streams
pub enum NetworkStream {
/// Plain TCP

View File

@@ -9,10 +9,10 @@
//!
//! * 8BITMIME ([RFC 6152](https://tools.ietf.org/html/rfc6152))
//! * AUTH ([RFC 4954](http://tools.ietf.org/html/rfc4954)) with PLAIN and CRAM-MD5 mecanisms
//! * STARTTLS ([RFC 2487](http://tools.ietf.org/html/rfc2487))
//!
//! It will eventually implement the following extensions:
//!
//! * STARTTLS ([RFC 2487](http://tools.ietf.org/html/rfc2487))
//! * SMTPUTF8 ([RFC 6531](http://tools.ietf.org/html/rfc6531))
//!
//! ## Architecture
@@ -57,6 +57,7 @@
//! use smtp::sender::{Sender, SenderBuilder};
//! use smtp::email::EmailBuilder;
//! use smtp::authentication::Mecanism;
//! use smtp::SUBMISSION_PORT;
//!
//! let mut builder = EmailBuilder::new();
//! builder = builder.to(("user@example.org", "Alias name"));
@@ -72,13 +73,13 @@
//! let email = builder.build();
//!
//! // Connect to a remote server on a custom port
//! let mut sender = SenderBuilder::new(("server.tld", 10025)).unwrap()
//! let mut sender = SenderBuilder::new(("server.tld", SUBMISSION_PORT)).unwrap()
//! // Set the name sent during EHLO/HELO, default is `localhost`
//! .hello_name("my.hostname.tld")
//! // Add credentials for authentication
//! .credentials("username", "password")
//! // Use smtps, you can also specify a specific SSL context with `.ssl_context(context)`
//! .ssl()
//! // Use TLS with STARTTLS, you can also specify a specific SSL context with `.ssl_context(context)`
//! .starttls()
//! // Configure accepted authetication mecanisms
//! .authentication_mecanisms(vec![Mecanism::CramMd5])
//! // Enable connection reuse
@@ -125,7 +126,7 @@
//! use smtp::client::net::NetworkStream;
//!
//! let mut email_client: Client<NetworkStream> = Client::new();
//! let _ = email_client.connect(&("localhost", SMTP_PORT), None);
//! let _ = email_client.connect(&("localhost", SMTP_PORT));
//! let _ = email_client.ehlo("my_hostname");
//! let _ = email_client.mail("user@example.com", None);
//! let _ = email_client.rcpt("user@example.org");

View File

@@ -57,15 +57,15 @@ impl SenderBuilder {
SenderBuilder::new(("localhost", SMTP_PORT))
}
/// Add an SSL context and try to use SSL connection
/// Use STARTTLS with a specific context
pub fn ssl_context(mut self, ssl_context: SslContext) -> SenderBuilder {
self.ssl_context = Some(ssl_context);
self
}
/// Add an SSL context and try to use SSL connection
pub fn ssl(self) -> SenderBuilder {
self.ssl_context(SslContext::new(SslMethod::Sslv23).unwrap())
/// Require SSL/TLS using STARTTLS
pub fn starttls(self) -> SenderBuilder {
self.ssl_context(SslContext::new(SslMethod::Tlsv1).unwrap())
}
/// Set the name used during HELO or EHLO
@@ -178,6 +178,19 @@ impl Sender {
self.client.close();
}
/// Gets the EHLO response and updates server information
pub fn get_ehlo(&mut self) -> SmtpResult {
// Extended Hello
let ehlo_response = try_smtp!(self.client.ehlo(&self.client_info.hello_name), self);
self.server_info = Some(try_smtp!(ServerInfo::from_response(&ehlo_response), self));
// Print server information
debug!("server {}", self.server_info.as_ref().unwrap());
Ok(ehlo_response)
}
/// Sends an email
pub fn send<T: SendableEmail>(&mut self, email: T) -> SmtpResult {
// Check if the connection is still available
@@ -189,48 +202,37 @@ impl Sender {
// 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, self.client_info.ssl_context.as_ref()));
try!(self.client.connect(&self.client_info.server_addr));
// Log the connection
info!("connection established to {}", self.client_info.server_addr);
// Extended Hello or Hello if needed
let hello_response = match self.client.ehlo(&self.client_info.hello_name) {
Ok(response) => response,
Err(error) => match error {
Error::PermanentError(ref response) if response.has_code(550) => {
match self.client.helo(&self.client_info.hello_name) {
Ok(response) => response,
Err(error) => try_smtp!(Err(error), self),
}
}
_ => {
try_smtp!(Err(error), self)
}
},
};
try!(self.get_ehlo());
self.server_info = Some(try_smtp!(ServerInfo::from_response(&hello_response), self));
if self.client_info.ssl_context.is_some() {
try_smtp!(self.client.starttls(), self);
// Print server information
debug!("server {}", self.server_info.as_ref().unwrap());
}
try!(self.client.upgrade_tls_stream(self.client_info.ssl_context.as_ref().unwrap()));
if self.client_info.credentials.is_some() && self.state.connection_reuse_count == 0 {
let (username, password) = self.client_info.credentials.clone().unwrap();
let mut found = false;
for mecanism in self.client_info.authentication_mecanisms.clone() {
if self.server_info.as_ref().unwrap().supports_auth_mecanism(mecanism) {
found = true;
let result = self.client.auth(mecanism, &username, &password);
try_smtp!(result, self);
}
try!(self.get_ehlo());
}
if !found {
debug!("No supported authentication mecanisms available");
if self.client_info.credentials.is_some() && self.state.connection_reuse_count == 0 {
let (username, password) = self.client_info.credentials.clone().unwrap();
let mut found = false;
for mecanism in self.client_info.authentication_mecanisms.clone() {
if self.server_info.as_ref().unwrap().supports_auth_mecanism(mecanism) {
found = true;
let result = self.client.auth(mecanism, &username, &password);
try_smtp!(result, self);
}
}
if !found {
debug!("No supported authentication mecanisms available");
}
}
}