Add STARTTLS support instead of SMTPS
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
11
src/lib.rs
11
src/lib.rs
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user