smtps with rust-openssl
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user