feat(transport): Use structured types for transports parameters
This commit is contained in:
@@ -3,12 +3,15 @@
|
||||
extern crate lettre;
|
||||
extern crate test;
|
||||
|
||||
use lettre::{ClientSecurity, SmtpTransport};
|
||||
use lettre::{EmailAddress, EmailTransport, SimpleSendableEmail};
|
||||
use lettre::smtp::SmtpTransportBuilder;
|
||||
use lettre::smtp::ConnectionReuseParameters;
|
||||
|
||||
#[bench]
|
||||
fn bench_simple_send(b: &mut test::Bencher) {
|
||||
let mut sender = SmtpTransportBuilder::new("127.0.0.1:2525").unwrap().build();
|
||||
let mut sender = SmtpTransport::builder("127.0.0.1:2525", ClientSecurity::None)
|
||||
.unwrap()
|
||||
.build();
|
||||
b.iter(|| {
|
||||
let email = SimpleSendableEmail::new(
|
||||
EmailAddress::new("user@localhost".to_string()),
|
||||
@@ -23,9 +26,9 @@ fn bench_simple_send(b: &mut test::Bencher) {
|
||||
|
||||
#[bench]
|
||||
fn bench_reuse_send(b: &mut test::Bencher) {
|
||||
let mut sender = SmtpTransportBuilder::new("127.0.0.1:2525")
|
||||
let mut sender = SmtpTransport::builder("127.0.0.1:2525", ClientSecurity::None)
|
||||
.unwrap()
|
||||
.connection_reuse(true)
|
||||
.connection_reuse(ConnectionReuseParameters::ReuseUnlimited)
|
||||
.build();
|
||||
b.iter(|| {
|
||||
let email = SimpleSendableEmail::new(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
extern crate lettre;
|
||||
|
||||
use lettre::{EmailAddress, EmailTransport, SecurityLevel, SimpleSendableEmail, SmtpTransport};
|
||||
use lettre::{EmailAddress, EmailTransport, SimpleSendableEmail, SmtpTransport};
|
||||
|
||||
fn main() {
|
||||
let email = SimpleSendableEmail::new(
|
||||
@@ -11,9 +11,8 @@ fn main() {
|
||||
);
|
||||
|
||||
// Open a local connection on port 25
|
||||
let mut mailer = SmtpTransport::builder_localhost()
|
||||
let mut mailer = SmtpTransport::builder_unencrypted_localhost()
|
||||
.unwrap()
|
||||
.security_level(SecurityLevel::Opportunistic)
|
||||
.build();
|
||||
// Send the email
|
||||
let result = mailer.send(email);
|
||||
|
||||
@@ -74,7 +74,9 @@ impl EmailTransport<FileResult> for FileEmailTransport {
|
||||
email.message(),
|
||||
);
|
||||
|
||||
f.write_all(serde_json::to_string(&simple_email)?.as_bytes())?;
|
||||
f.write_all(
|
||||
serde_json::to_string(&simple_email)?.as_bytes(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -32,8 +32,9 @@ pub mod file;
|
||||
#[cfg(feature = "file-transport")]
|
||||
pub use file::FileEmailTransport;
|
||||
pub use sendmail::SendmailTransport;
|
||||
pub use smtp::SecurityLevel;
|
||||
pub use smtp::ClientSecurity;
|
||||
pub use smtp::SmtpTransport;
|
||||
pub use smtp::client::net::ClientTlsParameters;
|
||||
|
||||
use std::fmt;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
@@ -33,16 +33,12 @@ pub struct SendmailTransport {
|
||||
impl SendmailTransport {
|
||||
/// Creates a new transport with the default `/usr/sbin/sendmail` command
|
||||
pub fn new() -> SendmailTransport {
|
||||
SendmailTransport {
|
||||
command: "/usr/sbin/sendmail".to_string(),
|
||||
}
|
||||
SendmailTransport { command: "/usr/sbin/sendmail".to_string() }
|
||||
}
|
||||
|
||||
/// Creates a new transport to the given sendmail command
|
||||
pub fn new_with_command<S: Into<String>>(command: S) -> SendmailTransport {
|
||||
SendmailTransport {
|
||||
command: command.into(),
|
||||
}
|
||||
SendmailTransport { command: command.into() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,21 +47,21 @@ impl EmailTransport<SendmailResult> for SendmailTransport {
|
||||
// Spawn the sendmail command
|
||||
let to_addresses: Vec<String> = email.to().iter().map(|x| x.to_string()).collect();
|
||||
let mut process = Command::new(&self.command)
|
||||
.args(&[
|
||||
"-i",
|
||||
"-f",
|
||||
&email.from().to_string(),
|
||||
&to_addresses.join(" "),
|
||||
])
|
||||
.args(
|
||||
&[
|
||||
"-i",
|
||||
"-f",
|
||||
&email.from().to_string(),
|
||||
&to_addresses.join(" "),
|
||||
],
|
||||
)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
match process
|
||||
.stdin
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.write_all(email.message().as_bytes()) {
|
||||
match process.stdin.as_mut().unwrap().write_all(
|
||||
email.message().as_bytes(),
|
||||
) {
|
||||
Ok(_) => (),
|
||||
Err(error) => return Err(From::from(error)),
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ mod test {
|
||||
mechanism
|
||||
.response(
|
||||
&credentials,
|
||||
Some("PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg==")
|
||||
Some("PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg=="),
|
||||
)
|
||||
.unwrap(),
|
||||
"alice a540ebe4ef2304070bbc3c456c1f64c0"
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
//! SMTP client
|
||||
|
||||
use bufstream::BufStream;
|
||||
use native_tls::TlsConnector;
|
||||
use smtp::{CRLF, MESSAGE_ENDING};
|
||||
use smtp::authentication::{Credentials, Mechanism};
|
||||
use smtp::client::net::{Connector, NetworkStream, Timeout};
|
||||
use smtp::client::net::{ClientTlsParameters, Connector, NetworkStream, Timeout};
|
||||
use smtp::commands::*;
|
||||
use smtp::error::{Error, SmtpResult};
|
||||
use smtp::response::ResponseParser;
|
||||
@@ -82,9 +81,9 @@ impl<S: Connector + Write + Read + Timeout + Debug> Client<S> {
|
||||
}
|
||||
|
||||
/// Upgrades the underlying connection to SSL/TLS
|
||||
pub fn upgrade_tls_stream(&mut self, tls_connector: &TlsConnector) -> io::Result<()> {
|
||||
pub fn upgrade_tls_stream(&mut self, tls_parameters: &ClientTlsParameters) -> io::Result<()> {
|
||||
match self.stream {
|
||||
Some(ref mut stream) => stream.get_mut().upgrade_tls(tls_connector),
|
||||
Some(ref mut stream) => stream.get_mut().upgrade_tls(tls_parameters),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
@@ -113,7 +112,7 @@ impl<S: Connector + Write + Read + Timeout + Debug> Client<S> {
|
||||
pub fn connect<A: ToSocketAddrs>(
|
||||
&mut self,
|
||||
addr: &A,
|
||||
tls_connector: Option<&TlsConnector>,
|
||||
tls_parameters: Option<&ClientTlsParameters>,
|
||||
) -> SmtpResult {
|
||||
// Connect should not be called when the client is already connected
|
||||
if self.stream.is_some() {
|
||||
@@ -130,7 +129,7 @@ impl<S: Connector + Write + Read + Timeout + Debug> Client<S> {
|
||||
debug!("connecting to {}", server_addr);
|
||||
|
||||
// Try to connect
|
||||
self.set_stream(Connector::connect(&server_addr, tls_connector)?);
|
||||
self.set_stream(Connector::connect(&server_addr, tls_parameters)?);
|
||||
|
||||
self.get_reply()
|
||||
}
|
||||
@@ -156,8 +155,9 @@ impl<S: Connector + Write + Read + Timeout + Debug> Client<S> {
|
||||
|
||||
// TODO
|
||||
let mut challenges = 10;
|
||||
let mut response =
|
||||
self.smtp_command(AuthCommand::new(mechanism, credentials.clone(), None)?)?;
|
||||
let mut response = self.smtp_command(
|
||||
AuthCommand::new(mechanism, credentials.clone(), None)?,
|
||||
)?;
|
||||
|
||||
while challenges > 0 && response.has_code(334) {
|
||||
challenges -= 1;
|
||||
|
||||
@@ -7,6 +7,25 @@ use std::io::{ErrorKind, Read, Write};
|
||||
use std::net::{Ipv4Addr, Shutdown, SocketAddr, SocketAddrV4, TcpStream};
|
||||
use std::time::Duration;
|
||||
|
||||
/// Parameters to use for secure clients
|
||||
#[derive(Clone)]
|
||||
pub struct ClientTlsParameters {
|
||||
/// A connector from `native-tls`
|
||||
pub connector: TlsConnector,
|
||||
/// The domain to send during the TLS handshake
|
||||
pub domain: String,
|
||||
}
|
||||
|
||||
impl ClientTlsParameters {
|
||||
/// TODO
|
||||
pub fn new(domain: String, connector: TlsConnector) -> ClientTlsParameters {
|
||||
ClientTlsParameters {
|
||||
connector: connector,
|
||||
domain: domain,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Represents the different types of underlying network streams
|
||||
pub enum NetworkStream {
|
||||
@@ -73,9 +92,10 @@ impl Write for NetworkStream {
|
||||
/// A trait for the concept of opening a stream
|
||||
pub trait Connector: Sized {
|
||||
/// Opens a connection to the given IP socket
|
||||
fn connect(addr: &SocketAddr, tls_connector: Option<&TlsConnector>) -> io::Result<Self>;
|
||||
fn connect(addr: &SocketAddr, tls_parameters: Option<&ClientTlsParameters>)
|
||||
-> io::Result<Self>;
|
||||
/// Upgrades to TLS connection
|
||||
fn upgrade_tls(&mut self, tls_connector: &TlsConnector) -> io::Result<()>;
|
||||
fn upgrade_tls(&mut self, tls_parameters: &ClientTlsParameters) -> io::Result<()>;
|
||||
/// Is the NetworkStream encrypted
|
||||
fn is_encrypted(&self) -> bool;
|
||||
}
|
||||
@@ -83,14 +103,15 @@ pub trait Connector: Sized {
|
||||
impl Connector for NetworkStream {
|
||||
fn connect(
|
||||
addr: &SocketAddr,
|
||||
tls_connector: Option<&TlsConnector>,
|
||||
tls_parameters: Option<&ClientTlsParameters>,
|
||||
) -> io::Result<NetworkStream> {
|
||||
let tcp_stream = TcpStream::connect(addr)?;
|
||||
|
||||
match tls_connector {
|
||||
match tls_parameters {
|
||||
Some(context) => {
|
||||
context
|
||||
.danger_connect_without_providing_domain_for_certificate_verification_and_server_name_indication(tcp_stream)
|
||||
.connector
|
||||
.connect(context.domain.as_ref(), tcp_stream)
|
||||
.map(NetworkStream::Tls)
|
||||
.map_err(|e| io::Error::new(ErrorKind::Other, e))
|
||||
}
|
||||
@@ -99,10 +120,13 @@ impl Connector for NetworkStream {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(match_same_arms))]
|
||||
fn upgrade_tls(&mut self, tls_connector: &TlsConnector) -> io::Result<()> {
|
||||
fn upgrade_tls(&mut self, tls_parameters: &ClientTlsParameters) -> io::Result<()> {
|
||||
*self = match *self {
|
||||
NetworkStream::Tcp(ref mut stream) => {
|
||||
match tls_connector.danger_connect_without_providing_domain_for_certificate_verification_and_server_name_indication(stream.try_clone().unwrap()) {
|
||||
match tls_parameters.connector.connect(
|
||||
tls_parameters.domain.as_ref(),
|
||||
stream.try_clone().unwrap(),
|
||||
) {
|
||||
Ok(tls_stream) => NetworkStream::Tls(tls_stream),
|
||||
Err(err) => return Err(io::Error::new(ErrorKind::Other, err)),
|
||||
}
|
||||
@@ -112,7 +136,6 @@ impl Connector for NetworkStream {
|
||||
};
|
||||
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(match_same_arms))]
|
||||
|
||||
@@ -27,9 +27,7 @@ impl Display for EhloCommand {
|
||||
impl EhloCommand {
|
||||
/// Creates a EHLO command
|
||||
pub fn new(client_id: ClientId) -> EhloCommand {
|
||||
EhloCommand {
|
||||
client_id: client_id,
|
||||
}
|
||||
EhloCommand { client_id: client_id }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,10 +250,10 @@ impl AuthCommand {
|
||||
challenge: Option<String>,
|
||||
) -> Result<AuthCommand, Error> {
|
||||
let response = if mechanism.supports_initial_response() || challenge.is_some() {
|
||||
Some(
|
||||
mechanism
|
||||
.response(&credentials, challenge.as_ref().map(String::as_str))?,
|
||||
)
|
||||
Some(mechanism.response(
|
||||
&credentials,
|
||||
challenge.as_ref().map(String::as_str),
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -297,10 +295,10 @@ impl AuthCommand {
|
||||
|
||||
debug!("auth decoded challenge: {}", decoded_challenge);
|
||||
|
||||
let response = Some(
|
||||
mechanism
|
||||
.response(&credentials, Some(decoded_challenge.as_ref()))?,
|
||||
);
|
||||
let response = Some(mechanism.response(
|
||||
&credentials,
|
||||
Some(decoded_challenge.as_ref()),
|
||||
)?);
|
||||
|
||||
Ok(AuthCommand {
|
||||
mechanism: mechanism,
|
||||
|
||||
@@ -153,8 +153,9 @@ impl ServerInfo {
|
||||
|
||||
/// Checks if the server supports an ESMTP feature
|
||||
pub fn supports_auth_mechanism(&self, mechanism: Mechanism) -> bool {
|
||||
self.features
|
||||
.contains(&Extension::Authentication(mechanism))
|
||||
self.features.contains(
|
||||
&Extension::Authentication(mechanism),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,9 +350,13 @@ mod test {
|
||||
|
||||
let mut features2 = HashSet::new();
|
||||
assert!(features2.insert(Extension::EightBitMime));
|
||||
assert!(features2.insert(Extension::Authentication(Mechanism::Plain),));
|
||||
assert!(features2.insert(
|
||||
Extension::Authentication(Mechanism::Plain),
|
||||
));
|
||||
#[cfg(feature = "crammd5-auth")]
|
||||
assert!(features2.insert(Extension::Authentication(Mechanism::CramMd5),));
|
||||
assert!(features2.insert(
|
||||
Extension::Authentication(Mechanism::CramMd5),
|
||||
));
|
||||
|
||||
let server_info2 = ServerInfo {
|
||||
name: "me".to_string(),
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//! This is the most basic example of usage:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use lettre::{SimpleSendableEmail, EmailTransport, EmailAddress, SmtpTransport, SecurityLevel};
|
||||
//! use lettre::{SimpleSendableEmail, EmailTransport, EmailAddress, SmtpTransport};
|
||||
//!
|
||||
//! let email = SimpleSendableEmail::new(
|
||||
//! EmailAddress::new("user@localhost".to_string()),
|
||||
@@ -29,8 +29,7 @@
|
||||
//!
|
||||
//! // Open a local connection on port 25
|
||||
//! let mut mailer =
|
||||
//! SmtpTransport::builder_localhost().unwrap()
|
||||
//! .security_level(SecurityLevel::Opportunistic).build();
|
||||
//! SmtpTransport::builder_unencrypted_localhost().unwrap().build();
|
||||
//! // Send the email
|
||||
//! let result = mailer.send(email);
|
||||
//!
|
||||
@@ -42,8 +41,10 @@
|
||||
//! ```rust,no_run
|
||||
//! use lettre::smtp::authentication::{Credentials, Mechanism};
|
||||
//! use lettre::smtp::SUBMISSION_PORT;
|
||||
//! use lettre::{SimpleSendableEmail, EmailTransport, EmailAddress, SmtpTransport, SecurityLevel};
|
||||
//! use lettre::{SimpleSendableEmail, EmailTransport, EmailAddress, SmtpTransport};
|
||||
//! use lettre::smtp::extension::ClientId;
|
||||
//! use lettre::smtp::ConnectionReuseParameters;
|
||||
//!
|
||||
//!
|
||||
//! let email = SimpleSendableEmail::new(
|
||||
//! EmailAddress::new("user@localhost".to_string()),
|
||||
@@ -53,23 +54,18 @@
|
||||
//! );
|
||||
//!
|
||||
//! // Connect to a remote server on a custom port
|
||||
//! let mut mailer = SmtpTransport::builder(("server.tld",
|
||||
//! SUBMISSION_PORT)).unwrap()
|
||||
//! let mut mailer = SmtpTransport::simple_builder("server.tld".to_string()).unwrap()
|
||||
//! // Set the name sent during EHLO/HELO, default is `localhost`
|
||||
//! .hello_name(ClientId::Domain("my.hostname.tld".to_string()))
|
||||
//! // Add credentials for authentication
|
||||
//! .credentials(Credentials::new("username".to_string(), "password".to_string()))
|
||||
//! // Specify a TLS security level. You can also specify an TlsConnector with
|
||||
//! // .tls_connector(TlsConnector::builder().unwrap()
|
||||
//! // .supported_protocols(&vec![Protocol::Tlsv12])
|
||||
//! // .build().unwrap())
|
||||
//! .security_level(SecurityLevel::AlwaysEncrypt)
|
||||
//! // FIXME security doc
|
||||
//! // Enable SMTPUTF8 if the server supports it
|
||||
//! .smtp_utf8(true)
|
||||
//! // Configure expected authentication mechanism
|
||||
//! .authentication_mechanism(Mechanism::Plain)
|
||||
//! // Enable connection reuse
|
||||
//! .connection_reuse(true).build();
|
||||
//! .connection_reuse(ConnectionReuseParameters::ReuseUnlimited).build();
|
||||
//!
|
||||
//! let result_1 = mailer.send(email.clone());
|
||||
//! assert!(result_1.is_ok());
|
||||
@@ -116,6 +112,7 @@ use native_tls::TlsConnector;
|
||||
use smtp::authentication::{Credentials, DEFAULT_ENCRYPTED_MECHANISMS,
|
||||
DEFAULT_UNENCRYPTED_MECHANISMS, Mechanism};
|
||||
use smtp::client::Client;
|
||||
use smtp::client::net::ClientTlsParameters;
|
||||
use smtp::commands::*;
|
||||
use smtp::error::{Error, SmtpResult};
|
||||
use smtp::extension::{ClientId, Extension, MailBodyParameter, MailParameter, ServerInfo};
|
||||
@@ -157,43 +154,43 @@ pub const MESSAGE_ENDING: &'static str = "\r\n.\r\n";
|
||||
/// NUL unicode character
|
||||
pub const NUL: &'static str = "\0";
|
||||
|
||||
/// TLS security level
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||
pub enum SecurityLevel {
|
||||
/// Use a TLS wrapped connection
|
||||
///
|
||||
/// How to apply TLS to a client connection
|
||||
#[derive(Clone)]
|
||||
pub enum ClientSecurity {
|
||||
/// Insecure connection
|
||||
None,
|
||||
/// Use `STARTTLS` when available
|
||||
Opportunistic(ClientTlsParameters),
|
||||
/// Always use `STARTTLS`
|
||||
Required(ClientTlsParameters),
|
||||
/// Use TLS wrapped connection without negotation
|
||||
/// Non RFC-compliant, should only be used if the server does not support STARTTLS.
|
||||
EncryptedWrapper,
|
||||
/// Only send an email on encrypted connection (with STARTTLS)
|
||||
///
|
||||
/// Default mode, prevents MITM when used with verified certificates.
|
||||
AlwaysEncrypt,
|
||||
/// Use TLS when available (with STARTTLS)
|
||||
///
|
||||
/// Should be used when not possible to always encrypt the connection
|
||||
Opportunistic,
|
||||
/// Never use encryption
|
||||
NeverEncrypt,
|
||||
Wrapper(ClientTlsParameters),
|
||||
}
|
||||
|
||||
/// Configures connection reuse behavior
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ConnectionReuseParameters {
|
||||
/// Unlimitied connection reuse
|
||||
ReuseUnlimited,
|
||||
/// Maximum number of connection reuse
|
||||
ReuseLimited(u16),
|
||||
/// Disable connection reuse, close connection after each transaction
|
||||
NoReuse,
|
||||
}
|
||||
|
||||
/// Contains client configuration
|
||||
pub struct SmtpTransportBuilder {
|
||||
/// Maximum connection reuse
|
||||
///
|
||||
/// Zero means no limitation
|
||||
connection_reuse_count_limit: u16,
|
||||
/// Enable connection reuse
|
||||
connection_reuse: bool,
|
||||
/// Name sent during HELO or EHLO
|
||||
connection_reuse: ConnectionReuseParameters,
|
||||
/// Name sent during EHLO
|
||||
hello_name: ClientId,
|
||||
/// Credentials
|
||||
credentials: Option<Credentials>,
|
||||
/// Socket we are connecting to
|
||||
server_addr: SocketAddr,
|
||||
/// SSL context to use
|
||||
tls_connector: TlsConnector,
|
||||
/// TLS security level
|
||||
security_level: SecurityLevel,
|
||||
/// TLS security configuration
|
||||
security: ClientSecurity,
|
||||
/// Enable UTF8 mailboxes in envelope or headers
|
||||
smtp_utf8: bool,
|
||||
/// Optional enforced authentication mechanism
|
||||
@@ -205,20 +202,29 @@ pub struct SmtpTransportBuilder {
|
||||
|
||||
/// Builder for the SMTP `SmtpTransport`
|
||||
impl SmtpTransportBuilder {
|
||||
/// Creates a new local SMTP client
|
||||
pub fn new<A: ToSocketAddrs>(addr: A) -> Result<SmtpTransportBuilder, Error> {
|
||||
/// Creates a new SMTP client
|
||||
///
|
||||
/// Defaults are:
|
||||
///
|
||||
/// * No connection reuse
|
||||
/// * "localhost" as EHLO name
|
||||
/// * No authentication
|
||||
/// * No SMTPUTF8 support
|
||||
/// * A 60 seconds timeout for smtp commands
|
||||
pub fn new<A: ToSocketAddrs>(
|
||||
addr: A,
|
||||
security: ClientSecurity,
|
||||
) -> Result<SmtpTransportBuilder, Error> {
|
||||
let mut addresses = addr.to_socket_addrs()?;
|
||||
|
||||
match addresses.next() {
|
||||
Some(addr) => {
|
||||
Ok(SmtpTransportBuilder {
|
||||
server_addr: addr,
|
||||
tls_connector: TlsConnector::builder().unwrap().build().unwrap(),
|
||||
security_level: SecurityLevel::AlwaysEncrypt,
|
||||
security: security,
|
||||
smtp_utf8: false,
|
||||
credentials: None,
|
||||
connection_reuse_count_limit: 100,
|
||||
connection_reuse: false,
|
||||
connection_reuse: ConnectionReuseParameters::NoReuse,
|
||||
hello_name: ClientId::Domain("localhost".to_string()),
|
||||
authentication_mechanism: None,
|
||||
timeout: Some(Duration::new(60, 0)),
|
||||
@@ -228,55 +234,24 @@ impl SmtpTransportBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Use STARTTLS with a specific context
|
||||
pub fn tls_connector(mut self, tls_context: TlsConnector) -> SmtpTransportBuilder {
|
||||
self.tls_connector = tls_context;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the security level for TLS
|
||||
pub fn security_level(mut self, level: SecurityLevel) -> SmtpTransportBuilder {
|
||||
self.security_level = level;
|
||||
self
|
||||
}
|
||||
|
||||
/// Require TLS using STARTTLS
|
||||
///
|
||||
/// Incompatible with `tls_wrapper()``
|
||||
pub fn encrypt(mut self) -> SmtpTransportBuilder {
|
||||
self.security_level = SecurityLevel::AlwaysEncrypt;
|
||||
self
|
||||
}
|
||||
|
||||
/// Require TLS using SMTPS
|
||||
///
|
||||
/// Incompatible with `encrypt()`
|
||||
pub fn tls_wrapper(mut self) -> SmtpTransportBuilder {
|
||||
self.security_level = SecurityLevel::EncryptedWrapper;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable SMTPUTF8 if the server supports it
|
||||
pub fn smtp_utf8(mut self, enabled: bool) -> SmtpTransportBuilder {
|
||||
self.smtp_utf8 = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the name used during HELO or EHLO
|
||||
/// Set the name used during EHLO
|
||||
pub fn hello_name(mut self, name: ClientId) -> SmtpTransportBuilder {
|
||||
self.hello_name = name;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable connection reuse
|
||||
pub fn connection_reuse(mut self, enable: bool) -> SmtpTransportBuilder {
|
||||
self.connection_reuse = enable;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the maximum number of emails sent using one connection
|
||||
pub fn connection_reuse_count_limit(mut self, limit: u16) -> SmtpTransportBuilder {
|
||||
self.connection_reuse_count_limit = limit;
|
||||
pub fn connection_reuse(
|
||||
mut self,
|
||||
parameters: ConnectionReuseParameters,
|
||||
) -> SmtpTransportBuilder {
|
||||
self.connection_reuse = parameters;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -286,7 +261,7 @@ impl SmtpTransportBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the authentication mechanisms
|
||||
/// Set the authentication mechanism to use
|
||||
pub fn authentication_mechanism(mut self, mechanism: Mechanism) -> SmtpTransportBuilder {
|
||||
self.authentication_mechanism = Some(mechanism);
|
||||
self
|
||||
@@ -344,13 +319,32 @@ macro_rules! try_smtp (
|
||||
);
|
||||
|
||||
impl SmtpTransport {
|
||||
/// TODO
|
||||
pub fn builder<A: ToSocketAddrs>(addr: A) -> Result<SmtpTransportBuilder, Error> {
|
||||
SmtpTransportBuilder::new(addr)
|
||||
/// Simple and secure transport, should be used when possible.
|
||||
/// Creates an encrypted transport over submission port, using the provided domain
|
||||
/// to validate TLS certificates.
|
||||
pub fn simple_builder(domain: String) -> Result<SmtpTransportBuilder, Error> {
|
||||
let tls_parameters = ClientTlsParameters::new(
|
||||
domain.clone(),
|
||||
TlsConnector::builder().unwrap().build().unwrap(),
|
||||
);
|
||||
|
||||
SmtpTransportBuilder::new(
|
||||
(domain.as_ref(), SUBMISSION_PORT),
|
||||
ClientSecurity::Required(tls_parameters),
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a new configurable builder
|
||||
pub fn builder<A: ToSocketAddrs>(
|
||||
addr: A,
|
||||
security: ClientSecurity,
|
||||
) -> Result<SmtpTransportBuilder, Error> {
|
||||
SmtpTransportBuilder::new(addr, security)
|
||||
}
|
||||
|
||||
/// Creates a new local SMTP client to port 25
|
||||
pub fn builder_localhost() -> Result<SmtpTransportBuilder, Error> {
|
||||
SmtpTransportBuilder::new(("localhost", SMTP_PORT))
|
||||
pub fn builder_unencrypted_localhost() -> Result<SmtpTransportBuilder, Error> {
|
||||
SmtpTransportBuilder::new(("localhost", SMTP_PORT), ClientSecurity::None)
|
||||
}
|
||||
|
||||
/// Creates a new SMTP client
|
||||
@@ -388,7 +382,7 @@ impl SmtpTransport {
|
||||
let ehlo_response = try_smtp!(
|
||||
self.client.smtp_command(EhloCommand::new(
|
||||
ClientId::new(self.client_info.hello_name.to_string()),
|
||||
),),
|
||||
)),
|
||||
self
|
||||
);
|
||||
|
||||
@@ -417,8 +411,8 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
|
||||
if self.state.connection_reuse_count == 0 {
|
||||
self.client.connect(
|
||||
&self.client_info.server_addr,
|
||||
match self.client_info.security_level {
|
||||
SecurityLevel::EncryptedWrapper => Some(&self.client_info.tls_connector),
|
||||
match self.client_info.security {
|
||||
ClientSecurity::Wrapper(ref tls_parameters) => Some(tls_parameters),
|
||||
_ => None,
|
||||
},
|
||||
)?;
|
||||
@@ -431,25 +425,21 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
|
||||
self.get_ehlo()?;
|
||||
|
||||
match (
|
||||
&self.client_info.security_level,
|
||||
self.server_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.supports_feature(Extension::StartTls),
|
||||
&self.client_info.security.clone(),
|
||||
self.server_info.as_ref().unwrap().supports_feature(
|
||||
Extension::StartTls,
|
||||
),
|
||||
) {
|
||||
(&SecurityLevel::AlwaysEncrypt, false) => {
|
||||
(&ClientSecurity::Required(_), false) => {
|
||||
return Err(From::from("Could not encrypt connection, aborting"))
|
||||
}
|
||||
(&SecurityLevel::Opportunistic, false) => (),
|
||||
(&SecurityLevel::NeverEncrypt, _) => (),
|
||||
(&SecurityLevel::EncryptedWrapper, _) => (),
|
||||
(_, true) => {
|
||||
(&ClientSecurity::Opportunistic(_), false) => (),
|
||||
(&ClientSecurity::None, _) => (),
|
||||
(&ClientSecurity::Wrapper(_), _) => (),
|
||||
(&ClientSecurity::Opportunistic(ref tls_parameters), true) |
|
||||
(&ClientSecurity::Required(ref tls_parameters), true) => {
|
||||
try_smtp!(self.client.smtp_command(StarttlsCommand), self);
|
||||
try_smtp!(
|
||||
self.client
|
||||
.upgrade_tls_stream(&self.client_info.tls_connector,),
|
||||
self
|
||||
);
|
||||
try_smtp!(self.client.upgrade_tls_stream(tls_parameters), self);
|
||||
|
||||
debug!("connection encrypted");
|
||||
|
||||
@@ -474,15 +464,16 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
|
||||
};
|
||||
|
||||
for mechanism in accepted_mechanisms {
|
||||
if self.server_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.supports_auth_mechanism(mechanism)
|
||||
if self.server_info.as_ref().unwrap().supports_auth_mechanism(
|
||||
mechanism,
|
||||
)
|
||||
{
|
||||
found = true;
|
||||
try_smtp!(
|
||||
self.client
|
||||
.auth(mechanism, self.client_info.credentials.as_ref().unwrap(),),
|
||||
self.client.auth(
|
||||
mechanism,
|
||||
self.client_info.credentials.as_ref().unwrap(),
|
||||
),
|
||||
self
|
||||
);
|
||||
break;
|
||||
@@ -498,25 +489,25 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
|
||||
// Mail
|
||||
let mut mail_options = vec![];
|
||||
|
||||
if self.server_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.supports_feature(Extension::EightBitMime)
|
||||
if self.server_info.as_ref().unwrap().supports_feature(
|
||||
Extension::EightBitMime,
|
||||
)
|
||||
{
|
||||
mail_options.push(MailParameter::Body(MailBodyParameter::EightBitMime));
|
||||
}
|
||||
|
||||
if self.server_info
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.supports_feature(Extension::SmtpUtfEight)
|
||||
if self.server_info.as_ref().unwrap().supports_feature(
|
||||
Extension::SmtpUtfEight,
|
||||
) && self.client_info.smtp_utf8
|
||||
{
|
||||
mail_options.push(MailParameter::SmtpUtfEight);
|
||||
}
|
||||
|
||||
try_smtp!(
|
||||
self.client
|
||||
.smtp_command(MailCommand::new(Some(email.from().clone()), mail_options,),),
|
||||
self.client.smtp_command(MailCommand::new(
|
||||
Some(email.from().clone()),
|
||||
mail_options,
|
||||
)),
|
||||
self
|
||||
);
|
||||
|
||||
@@ -526,8 +517,9 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
|
||||
// Recipient
|
||||
for to_address in &email.to() {
|
||||
try_smtp!(
|
||||
self.client
|
||||
.smtp_command(RcptCommand::new(to_address.clone(), vec![]),),
|
||||
self.client.smtp_command(
|
||||
RcptCommand::new(to_address.clone(), vec![]),
|
||||
),
|
||||
self
|
||||
);
|
||||
// Log the rcpt command
|
||||
@@ -563,10 +555,11 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
|
||||
}
|
||||
|
||||
// Test if we can reuse the existing connection
|
||||
if (!self.client_info.connection_reuse) ||
|
||||
(self.state.connection_reuse_count >= self.client_info.connection_reuse_count_limit)
|
||||
{
|
||||
self.reset();
|
||||
match self.client_info.connection_reuse {
|
||||
ConnectionReuseParameters::ReuseLimited(limit)
|
||||
if self.state.connection_reuse_count >= limit => self.reset(),
|
||||
ConnectionReuseParameters::NoReuse => self.reset(),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
result
|
||||
|
||||
@@ -274,9 +274,9 @@ impl Response {
|
||||
|
||||
/// Returns only the first word of the message if possible
|
||||
pub fn first_word(&self) -> Option<&str> {
|
||||
self.message
|
||||
.get(0)
|
||||
.and_then(|line| line.split_whitespace().next())
|
||||
self.message.get(0).and_then(
|
||||
|line| line.split_whitespace().next(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns only the line of the message if possible
|
||||
|
||||
@@ -39,7 +39,8 @@ mod tests {
|
||||
("bjørn", "bjørn"),
|
||||
("Ø+= ❤️‰", "Ø+2B+3D+20❤️‰"),
|
||||
("+", "+2B"),
|
||||
] {
|
||||
]
|
||||
{
|
||||
assert_eq!(format!("{}", XText(input)), expect);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
extern crate lettre;
|
||||
|
||||
use lettre::{EmailAddress, EmailTransport, SimpleSendableEmail};
|
||||
use lettre::smtp::SecurityLevel;
|
||||
use lettre::smtp::SmtpTransportBuilder;
|
||||
use lettre::{ClientSecurity, EmailAddress, EmailTransport, SimpleSendableEmail, SmtpTransport};
|
||||
|
||||
#[test]
|
||||
fn smtp_transport_simple() {
|
||||
let mut sender = SmtpTransportBuilder::new("127.0.0.1:2525")
|
||||
let mut sender = SmtpTransport::builder("127.0.0.1:2525", ClientSecurity::None)
|
||||
.unwrap()
|
||||
.security_level(SecurityLevel::Opportunistic)
|
||||
.build();
|
||||
let email = SimpleSendableEmail::new(
|
||||
EmailAddress::new("user@localhost".to_string()),
|
||||
|
||||
@@ -16,7 +16,9 @@ fn main() {
|
||||
.unwrap();
|
||||
|
||||
// Open a local connection on port 25
|
||||
let mut mailer = SmtpTransport::builder_localhost().unwrap().build();
|
||||
let mut mailer = SmtpTransport::builder_unencrypted_localhost()
|
||||
.unwrap()
|
||||
.build();
|
||||
// Send the email
|
||||
let result = mailer.send(email);
|
||||
|
||||
|
||||
@@ -391,9 +391,7 @@ impl Display for Email {
|
||||
impl PartBuilder {
|
||||
/// Creates a new empty part
|
||||
pub fn new() -> PartBuilder {
|
||||
PartBuilder {
|
||||
message: MimeMessage::new_blank_message(),
|
||||
}
|
||||
PartBuilder { message: MimeMessage::new_blank_message() }
|
||||
}
|
||||
|
||||
/// Adds a generic header
|
||||
@@ -576,8 +574,9 @@ impl EmailBuilder {
|
||||
|
||||
/// Adds a `Subject` header
|
||||
pub fn set_subject<S: Into<String>>(&mut self, subject: S) {
|
||||
self.message
|
||||
.add_header(("Subject".to_string(), subject.into()));
|
||||
self.message.add_header(
|
||||
("Subject".to_string(), subject.into()),
|
||||
);
|
||||
}
|
||||
|
||||
/// Adds a `Date` header with the given date
|
||||
@@ -588,8 +587,9 @@ impl EmailBuilder {
|
||||
|
||||
/// Adds a `Date` header with the given date
|
||||
pub fn set_date(&mut self, date: &Tm) {
|
||||
self.message
|
||||
.add_header(("Date", Tm::rfc822z(date).to_string()));
|
||||
self.message.add_header(
|
||||
("Date", Tm::rfc822z(date).to_string()),
|
||||
);
|
||||
self.date_issued = true;
|
||||
}
|
||||
|
||||
@@ -639,8 +639,10 @@ impl EmailBuilder {
|
||||
/// Sets the email body to HTML content
|
||||
pub fn set_html<S: Into<String>>(&mut self, body: S) {
|
||||
self.message.set_body(body);
|
||||
self.message
|
||||
.add_header(("Content-Type", format!("{}", mime::TEXT_HTML).as_ref()));
|
||||
self.message.add_header((
|
||||
"Content-Type",
|
||||
format!("{}", mime::TEXT_HTML).as_ref(),
|
||||
));
|
||||
}
|
||||
|
||||
/// Sets the email content
|
||||
@@ -725,10 +727,9 @@ impl EmailBuilder {
|
||||
// we need to generate the envelope
|
||||
let mut e = Envelope::new();
|
||||
// add all receivers in to_header and cc_header
|
||||
for receiver in self.to_header
|
||||
.iter()
|
||||
.chain(self.cc_header.iter())
|
||||
.chain(self.bcc_header.iter())
|
||||
for receiver in self.to_header.iter().chain(self.cc_header.iter()).chain(
|
||||
self.bcc_header.iter(),
|
||||
)
|
||||
{
|
||||
match *receiver {
|
||||
Address::Mailbox(ref m) => e.add_to(m.address.clone()),
|
||||
@@ -769,8 +770,12 @@ impl EmailBuilder {
|
||||
// Add the collected addresses as mailbox-list all at once.
|
||||
// The unwraps are fine because the conversions for Vec<Address> never errs.
|
||||
if !self.to_header.is_empty() {
|
||||
self.message
|
||||
.add_header(Header::new_with_value("To".into(), self.to_header).unwrap());
|
||||
self.message.add_header(
|
||||
Header::new_with_value(
|
||||
"To".into(),
|
||||
self.to_header,
|
||||
).unwrap(),
|
||||
);
|
||||
}
|
||||
if !self.from_header.is_empty() {
|
||||
self.message.add_header(
|
||||
@@ -780,8 +785,12 @@ impl EmailBuilder {
|
||||
return Err(Error::MissingFrom);
|
||||
}
|
||||
if !self.cc_header.is_empty() {
|
||||
self.message
|
||||
.add_header(Header::new_with_value("Cc".into(), self.cc_header).unwrap());
|
||||
self.message.add_header(
|
||||
Header::new_with_value(
|
||||
"Cc".into(),
|
||||
self.cc_header,
|
||||
).unwrap(),
|
||||
);
|
||||
}
|
||||
if !self.reply_to_header.is_empty() {
|
||||
self.message.add_header(
|
||||
@@ -790,8 +799,10 @@ impl EmailBuilder {
|
||||
}
|
||||
|
||||
if !self.date_issued {
|
||||
self.message
|
||||
.add_header(("Date", Tm::rfc822z(&now()).to_string().as_ref()));
|
||||
self.message.add_header((
|
||||
"Date",
|
||||
Tm::rfc822z(&now()).to_string().as_ref(),
|
||||
));
|
||||
}
|
||||
|
||||
self.message.add_header(("MIME-Version", "1.0"));
|
||||
@@ -801,7 +812,8 @@ impl EmailBuilder {
|
||||
if let Ok(header) = Header::new_with_value(
|
||||
"Message-ID".to_string(),
|
||||
format!("<{}.lettre@localhost>", message_id),
|
||||
) {
|
||||
)
|
||||
{
|
||||
self.message.add_header(header)
|
||||
}
|
||||
|
||||
@@ -922,7 +934,8 @@ mod test {
|
||||
);
|
||||
|
||||
email.message.headers.insert(
|
||||
Header::new_with_value("To".to_string(), "to@example.com".to_string()).unwrap(),
|
||||
Header::new_with_value("To".to_string(), "to@example.com".to_string())
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
email.message.body = "body".to_string();
|
||||
|
||||
Reference in New Issue
Block a user