feat(transport): Make use of hex and rust-crypto for crammd5 an optionnal feature

This commit is contained in:
Alexis Mousset
2017-07-17 16:18:33 +02:00
parent e90fe50943
commit e656e9e325
8 changed files with 83 additions and 48 deletions

View File

@@ -19,8 +19,8 @@ bufstream = "^0.1"
log = "^0.3"
native-tls = "^0.1"
base64 = "^0.6"
hex = "^0.2"
rust-crypto = "^0.2"
hex = { version = "^0.2", optional = true }
rust-crypto = { version = "^0.2", optional = true }
serde = { version = "^1.0", optional = true }
serde_json = { version = "^1.0", optional = true }
serde_derive = { version = "^1.0", optional = true }
@@ -29,7 +29,8 @@ serde_derive = { version = "^1.0", optional = true }
env_logger = "^0.4"
[features]
default = ["file-transport"]
default = ["file-transport", "crammd5-auth"]
unstable = []
serde-impls = ["serde", "serde_derive"]
file-transport = ["serde-impls", "serde_json"]
crammd5-auth = ["rust-crypto", "hex"]

View File

@@ -9,7 +9,9 @@
#[macro_use]
extern crate log;
extern crate base64;
#[cfg(feature = "crammd5-auth")]
extern crate hex;
#[cfg(feature = "crammd5-auth")]
extern crate crypto;
extern crate bufstream;
extern crate native_tls;

View File

@@ -1,14 +1,37 @@
//! Provides authentication mechanisms
#[cfg(feature = "crammd5-auth")]
use crypto::hmac::Hmac;
#[cfg(feature = "crammd5-auth")]
use crypto::mac::Mac;
#[cfg(feature = "crammd5-auth")]
use crypto::md5::Md5;
#[cfg(feature = "crammd5-auth")]
use hex::ToHex;
use smtp::NUL;
use smtp::error::Error;
use std::fmt;
use std::fmt::{Display, Formatter};
/// Accepted authentication mecanisms on an encrypted connection
/// Trying LOGIN last as it is deprecated.
#[cfg(feature = "crammd5-auth")]
pub const DEFAULT_ENCRYPTED_MECHANISMS: &'static [Mechanism] =
&[Mechanism::Plain, Mechanism::CramMd5, Mechanism::Login];
/// Accepted authentication mecanisms on an encrypted connection
/// Trying LOGIN last as it is deprecated.
#[cfg(not(feature = "crammd5-auth"))]
pub const DEFAULT_ENCRYPTED_MECHANISMS: &'static [Mechanism] = &[Mechanism::Plain, Mechanism::Login];
/// Accepted authentication mecanisms on an unencrypted connection
#[cfg(feature = "crammd5-auth")]
pub const DEFAULT_UNENCRYPTED_MECHANISMS: &'static [Mechanism] = &[Mechanism::CramMd5];
/// Accepted authentication mecanisms on an unencrypted connection
/// When CRAMMD5 support is not enabled, no mechanisms are allowed.
#[cfg(not(feature = "crammd5-auth"))]
pub const DEFAULT_UNENCRYPTED_MECHANISMS: &'static [Mechanism] = &[];
/// Convertable to user credentials
pub trait IntoCredentials {
/// Converts to a `Credentials` struct
@@ -57,6 +80,7 @@ pub enum Mechanism {
Login,
/// CRAM-MD5 authentication mechanism
/// RFC 2195: https://tools.ietf.org/html/rfc2195
#[cfg(feature = "crammd5-auth")]
CramMd5,
}
@@ -68,6 +92,7 @@ impl Display for Mechanism {
match *self {
Mechanism::Plain => "PLAIN",
Mechanism::Login => "LOGIN",
#[cfg(feature = "crammd5-auth")]
Mechanism::CramMd5 => "CRAM-MD5",
}
)
@@ -76,10 +101,12 @@ impl Display for Mechanism {
impl Mechanism {
/// Does the mechanism supports initial response
#[cfg_attr(feature = "cargo-clippy", allow(match_same_arms))]
pub fn supports_initial_response(&self) -> bool {
match *self {
Mechanism::Plain => true,
Mechanism::Login |
Mechanism::Login => false,
#[cfg(feature = "crammd5-auth")]
Mechanism::CramMd5 => false,
}
}
@@ -120,6 +147,7 @@ impl Mechanism {
Err(Error::Client("Unrecognized challenge"))
}
#[cfg(feature = "crammd5-auth")]
Mechanism::CramMd5 => {
let decoded_challenge = match challenge {
Some(challenge) => challenge,
@@ -174,6 +202,7 @@ mod test {
}
#[test]
#[cfg(feature = "crammd5-auth")]
fn test_cram_md5() {
let mechanism = Mechanism::CramMd5;

View File

@@ -317,7 +317,9 @@ impl AuthCommand {
mod test {
use super::*;
use smtp::extension::MailBodyParameter;
#[cfg(feature = "crammd5-auth")]
use smtp::response::Code;
#[cfg(feature = "crammd5-auth")]
use std::str::FromStr;
#[test]
@@ -395,6 +397,7 @@ mod test {
),
"AUTH PLAIN AHVzZXIAcGFzc3dvcmQ=\r\n"
);
#[cfg(feature = "crammd5-auth")]
assert_eq!(
format!(
"{}",
@@ -413,6 +416,7 @@ mod test {
),
"AUTH LOGIN\r\n"
);
#[cfg(feature = "crammd5-auth")]
assert_eq!(
format!(
"{}",

View File

@@ -128,6 +128,7 @@ impl ServerInfo {
"LOGIN" => {
features.insert(Extension::Authentication(Mechanism::Login));
}
#[cfg(feature = "crammd5-auth")]
"CRAM-MD5" => {
features.insert(Extension::Authentication(Mechanism::CramMd5));
}
@@ -330,6 +331,7 @@ mod test {
assert!(server_info.supports_feature(Extension::EightBitMime));
assert!(!server_info.supports_feature(Extension::StartTls));
#[cfg(feature = "crammd5-auth")]
assert!(!server_info.supports_auth_mechanism(Mechanism::CramMd5));
let response2 = Response::new(
@@ -351,6 +353,7 @@ mod test {
assert!(features2.insert(
Extension::Authentication(Mechanism::Plain),
));
#[cfg(feature = "crammd5-auth")]
assert!(features2.insert(
Extension::Authentication(Mechanism::CramMd5),
));
@@ -364,6 +367,7 @@ mod test {
assert!(server_info2.supports_feature(Extension::EightBitMime));
assert!(server_info2.supports_auth_mechanism(Mechanism::Plain));
#[cfg(feature = "crammd5-auth")]
assert!(server_info2.supports_auth_mechanism(Mechanism::CramMd5));
assert!(!server_info2.supports_feature(Extension::StartTls));
}

View File

@@ -68,7 +68,7 @@
//! // Enable SMTPUTF8 if the server supports it
//! .smtp_utf8(true)
//! // Configure expected authentication mechanism
//! .authentication_mechanism(Mechanism::CramMd5)
//! .authentication_mechanism(Mechanism::Plain)
//! // Enable connection reuse
//! .connection_reuse(true).build();
//!
@@ -110,7 +110,8 @@
use EmailTransport;
use SendableEmail;
use native_tls::TlsConnector;
use smtp::authentication::{Credentials, Mechanism};
use smtp::authentication::{Credentials, DEFAULT_ENCRYPTED_MECHANISMS,
DEFAULT_UNENCRYPTED_MECHANISMS, Mechanism};
use smtp::client::Client;
use smtp::commands::*;
use smtp::error::{Error, SmtpResult};
@@ -458,13 +459,9 @@ impl EmailTransport<SmtpResult> for SmtpTransport {
Some(mechanism) => vec![mechanism],
None => {
if self.client.is_encrypted() {
// If encrypted, allow all mechanisms, with a preference for the
// simplest
// Login is obsolete so try it last
vec![Mechanism::Plain, Mechanism::CramMd5, Mechanism::Login]
DEFAULT_ENCRYPTED_MECHANISMS.to_vec()
} else {
// If not encrypted, do not allow clear-text passwords by default
vec![Mechanism::CramMd5]
DEFAULT_UNENCRYPTED_MECHANISMS.to_vec()
}
}
};

View File

@@ -1,7 +0,0 @@
extern crate lettre;
mod transport_smtp;
mod transport_sendmail;
mod transport_stub;
#[cfg(feature = "file-transport")]
mod transport_file;

View File

@@ -1,37 +1,42 @@
extern crate lettre;
use lettre::{EmailAddress, EmailTransport, SendableEmail, SimpleSendableEmail};
#[cfg(test)]
#[cfg(feature = "file-transport")]
use lettre::file::FileEmailTransport;
use std::env::temp_dir;
use std::fs::File;
use std::fs::remove_file;
use std::io::Read;
mod test {
#[test]
#[cfg(feature = "file-transport")]
fn file_transport() {
let mut sender = FileEmailTransport::new(temp_dir());
let email = SimpleSendableEmail::new(
EmailAddress::new("user@localhost".to_string()),
vec![EmailAddress::new("root@localhost".to_string())],
"file_id".to_string(),
"Hello file".to_string(),
);
let result = sender.send(email.clone());
assert!(result.is_ok());
use lettre::{EmailAddress, EmailTransport, SendableEmail, SimpleSendableEmail};
#[cfg(feature = "file-transport")]
use lettre::file::FileEmailTransport;
use std::env::temp_dir;
use std::fs::File;
use std::fs::remove_file;
use std::io::Read;
let message_id = email.message_id();
let file = format!("{}/{}.txt", temp_dir().to_str().unwrap(), message_id);
let mut f = File::open(file.clone()).unwrap();
let mut buffer = String::new();
let _ = f.read_to_string(&mut buffer);
#[test]
#[cfg(feature = "file-transport")]
fn file_transport() {
let mut sender = FileEmailTransport::new(temp_dir());
let email = SimpleSendableEmail::new(
EmailAddress::new("user@localhost".to_string()),
vec![EmailAddress::new("root@localhost".to_string())],
"file_id".to_string(),
"Hello file".to_string(),
);
let result = sender.send(email.clone());
assert!(result.is_ok());
assert_eq!(
buffer,
"{\"to\":[\"root@localhost\"],\"from\":\"user@localhost\",\
\"message_id\":\"file_id\",\"message\":\"Hello file\"}"
);
let message_id = email.message_id();
let file = format!("{}/{}.txt", temp_dir().to_str().unwrap(), message_id);
let mut f = File::open(file.clone()).unwrap();
let mut buffer = String::new();
let _ = f.read_to_string(&mut buffer);
remove_file(file).unwrap();
assert_eq!(
buffer,
"{\"to\":[\"root@localhost\"],\"from\":\"user@localhost\",\
\"message_id\":\"file_id\",\"message\":\"Hello file\"}"
);
remove_file(file).unwrap();
}
}