smtps with rust-openssl

This commit is contained in:
Alexis Mousset
2015-10-11 19:56:02 +02:00
parent 5d125bdbdb
commit fefb5f7978
6 changed files with 120 additions and 40 deletions

View File

@@ -1,7 +1,7 @@
[package]
name = "smtp"
version = "0.2.1"
version = "0.3.0"
description = "Simple SMTP client"
readme = "README.md"
documentation = "http://amousset.me/rust-smtp/smtp/"
@@ -18,6 +18,7 @@ rustc-serialize = "0.3"
rust-crypto = "0.2"
bufstream = "0.1"
email = "0.0"
openssl = "0.6"
[dev-dependencies]
env_logger = "0.3"

View File

@@ -17,7 +17,7 @@ fn main() {
let mut threads = Vec::new();
for _ in 1..5 {
let th_sender = sender.clone();
threads.push(thread::spawn(move || {
@@ -27,15 +27,15 @@ fn main() {
.body("Hello World!")
.subject("Hello")
.build();
let _ = th_sender.lock().unwrap().send(email);
}));
}
for thread in threads {
let _ = thread.join();
}
let email = EmailBuilder::new()
.to("user@localhost")
.from("user@localhost")

View File

@@ -1,15 +1,16 @@
//! SMTP client
use std::string::String;
use std::net::{SocketAddr, ToSocketAddrs};
use std::net::ToSocketAddrs;
use std::io::{BufRead, Read, Write};
use bufstream::BufStream;
use openssl::ssl::SslContext;
use response::ResponseParser;
use authentication::Mecanism;
use error::{Error, SmtpResult};
use client::net::{Connector, SmtpStream};
use client::net::{Connector, NetworkStream};
use {CRLF, MESSAGE_ENDING};
pub mod net;
@@ -41,12 +42,10 @@ fn remove_crlf(string: &str) -> String {
}
/// Structure that implements the SMTP client
pub struct Client<S: Write + Read = SmtpStream> {
pub struct Client<S: Write + Read = NetworkStream> {
/// TCP stream between client and server
/// Value is None before connection
stream: Option<BufStream<S>>,
/// Socket we are connecting to
server_addr: SocketAddr,
}
macro_rules! return_err (
@@ -55,24 +54,18 @@ macro_rules! return_err (
})
);
impl<S: Write + Read = SmtpStream> Client<S> {
impl<S: Write + Read = NetworkStream> Client<S> {
/// Creates a new SMTP client
///
/// It does not connects to the server, but only creates the `Client`
pub fn new<A: ToSocketAddrs>(addr: A) -> Result<Client<S>, Error> {
let mut addresses = try!(addr.to_socket_addrs());
match addresses.next() {
Some(addr) => Ok(Client {
stream: None,
server_addr: addr,
}),
None => Err(From::from("Could nor resolve hostname")),
pub fn new() -> Client<S> {
Client {
stream: None,
}
}
}
impl<S: Connector + Write + Read = SmtpStream> Client<S> {
impl<S: Connector + Write + Read = NetworkStream> Client<S> {
/// Closes the SMTP transaction if possible
pub fn close(&mut self) {
let _ = self.quit();
@@ -80,14 +73,21 @@ impl<S: Connector + Write + Read = SmtpStream> Client<S> {
}
/// Connects to the configured server
pub fn connect(&mut self) -> SmtpResult {
pub fn connect<A: ToSocketAddrs>(&mut self, addr: &A, ssl_context: Option<&SslContext>) -> 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);
}
let mut addresses = try!(addr.to_socket_addrs());
let server_addr = match addresses.next() {
Some(addr) => addr,
None => return_err!("Could not resolve hostname", self),
};
// Try to connect
self.stream = Some(BufStream::new(try!(Connector::connect(&self.server_addr))));
self.stream = Some(BufStream::new(try!(Connector::connect(&server_addr, ssl_context))));
self.get_reply()
}
@@ -176,11 +176,13 @@ impl<S: Connector + Write + Read = SmtpStream> Client<S> {
None => return Err(Error::ResponseParsingError("Could not read CRAM challenge")),
};
debug!("CRAM challenge: {}", encoded_challenge);
let cram_response = try!(mecanism.response(username,
password,
Some(&encoded_challenge)));
self.command(&format!("AUTH CRAM-MD5 {}", cram_response))
self.command(&format!("{}", cram_response))
}
}
@@ -211,6 +213,8 @@ impl<S: Connector + Write + Read = SmtpStream> Client<S> {
let mut line = String::new();
try!(self.stream.as_mut().unwrap().read_line(&mut line));
debug!("Read: {}", escape_crlf(line.as_ref()));
while try!(parser.read_line(remove_crlf(line.as_ref()).as_ref())) {
line.clear();
try!(self.stream.as_mut().unwrap().read_line(&mut line));

View File

@@ -1,21 +1,80 @@
//! A trait to represent a stream
use std::io;
use std::io::{Read, Write, ErrorKind};
use std::net::SocketAddr;
use std::net::TcpStream;
use std::fmt;
use std::fmt::{Debug, Formatter};
use openssl::ssl::{SslContext, SslStream};
/// A trait for the concept of opening a stream
pub trait Connector {
/// Opens a connection to the given IP socket
fn connect(addr: &SocketAddr) -> io::Result<Self>;
fn connect(addr: &SocketAddr, ssl_context: Option<&SslContext>) -> io::Result<Self>;
}
impl Connector for SmtpStream {
fn connect(addr: &SocketAddr) -> io::Result<SmtpStream> {
TcpStream::connect(addr)
impl Connector for NetworkStream {
fn connect(addr: &SocketAddr, ssl_context: Option<&SslContext>) -> io::Result<NetworkStream> {
let tcp_stream = try!(TcpStream::connect(addr));
match ssl_context {
Some(context) => match SslStream::new(&context, tcp_stream) {
Ok(stream) => Ok(NetworkStream::Ssl(stream)),
Err(err) => Err(io::Error::new(ErrorKind::Other, err)),
},
None => Ok(NetworkStream::Plain(tcp_stream)),
}
}
}
/// Represents an atual SMTP network stream
//Used later for ssl
pub type SmtpStream = TcpStream;
/// TODO
pub enum NetworkStream {
/// TODO
Plain(TcpStream),
/// TODO
Ssl(SslStream<TcpStream>),
}
impl Clone for NetworkStream {
#[inline]
fn clone(&self) -> NetworkStream {
match self {
&NetworkStream::Plain(ref stream) => NetworkStream::Plain(stream.try_clone().unwrap()),
&NetworkStream::Ssl(ref stream) => NetworkStream::Ssl(stream.try_clone().unwrap()),
}
}
}
impl Debug for NetworkStream {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("NetworkStream(_)")
}
}
impl Read for NetworkStream {
#[inline]
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match *self {
NetworkStream::Plain(ref mut stream) => stream.read(buf),
NetworkStream::Ssl(ref mut stream) => stream.read(buf),
}
}
}
impl Write for NetworkStream {
#[inline]
fn write(&mut self, msg: &[u8]) -> io::Result<usize> {
match *self {
NetworkStream::Plain(ref mut stream) => stream.write(msg),
NetworkStream::Ssl(ref mut stream) => stream.write(msg),
}
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
match *self {
NetworkStream::Plain(ref mut stream) => stream.flush(),
NetworkStream::Ssl(ref mut stream) => stream.flush(),
}
}
}

View File

@@ -119,12 +119,11 @@
//!
//! ```rust,no_run
//! use smtp::client::Client;
//! use smtp::client::net::SmtpStream;
//! use smtp::SMTP_PORT;
//! use std::net::TcpStream;
//! use smtp::client::net::NetworkStream;
//!
//! let mut email_client: Client<SmtpStream> = Client::new(("localhost", SMTP_PORT)).unwrap();
//! let _ = email_client.connect();
//! let mut email_client: Client<NetworkStream> = Client::new();
//! 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");
@@ -143,6 +142,7 @@ extern crate time;
extern crate uuid;
extern crate email as email_format;
extern crate bufstream;
extern crate openssl;
mod extension;
pub mod client;

View File

@@ -3,12 +3,13 @@
use std::string::String;
use std::net::{SocketAddr, ToSocketAddrs};
use openssl::ssl::{SslMethod, SslContext};
use SMTP_PORT;
use extension::{Extension, ServerInfo};
use error::{SmtpResult, Error};
use email::SendableEmail;
use client::Client;
use client::net::SmtpStream;
use authentication::Mecanism;
/// Contains client configuration
@@ -25,6 +26,8 @@ pub struct SenderBuilder {
credentials: Option<(String, String)>,
/// Socket we are connecting to
server_addr: SocketAddr,
/// SSL contexyt to use
ssl_context: Option<SslContext>,
/// List of authentication mecanism, sorted by priority
authentication_mecanisms: Vec<Mecanism>,
}
@@ -35,10 +38,10 @@ impl SenderBuilder {
pub fn new<A: ToSocketAddrs>(addr: A) -> Result<SenderBuilder, Error> {
let mut addresses = try!(addr.to_socket_addrs());
match addresses.next() {
Some(addr) => Ok(SenderBuilder {
server_addr: addr,
ssl_context: None,
credentials: None,
connection_reuse_count_limit: 100,
enable_connection_reuse: false,
@@ -54,6 +57,17 @@ impl SenderBuilder {
SenderBuilder::new(("localhost", SMTP_PORT))
}
/// Add an SSL context and try to use SSL connection
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())
}
/// Set the name used during HELO or EHLO
pub fn hello_name(mut self, name: &str) -> SenderBuilder {
self.hello_name = name.to_string();
@@ -111,7 +125,7 @@ pub struct Sender {
/// Information about the client
client_info: SenderBuilder,
/// Low level client
client: Client<SmtpStream>,
client: Client,
}
macro_rules! try_smtp (
@@ -134,7 +148,9 @@ impl Sender {
///
/// It does not connects to the server, but only creates the `Sender`
pub fn new(builder: SenderBuilder) -> Sender {
let client: Client<SmtpStream> = Client::new(builder.server_addr).unwrap();
let client = Client::new();
Sender {
client: client,
server_info: None,
@@ -173,7 +189,7 @@ 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());
try!(self.client.connect(&self.client_info.server_addr, self.client_info.ssl_context.as_ref()));
// Log the connection
info!("connection established to {}", self.client_info.server_addr);