Refactoring
This commit is contained in:
@@ -9,6 +9,9 @@
|
||||
|
||||
//! Provides authentication mecanisms
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fmt::Result as FmtResult;
|
||||
|
||||
use serialize::base64::{self, ToBase64, FromBase64};
|
||||
use serialize::hex::ToHex;
|
||||
use crypto::hmac::Hmac;
|
||||
@@ -17,84 +20,83 @@ use crypto::mac::Mac;
|
||||
|
||||
use NUL;
|
||||
use error::Error;
|
||||
use extension::Extension;
|
||||
|
||||
/// Represents an authentication mecanism
|
||||
pub trait Mecanism {
|
||||
/// Returns the matching `Extension`
|
||||
fn extension() -> Extension;
|
||||
/// Returns the initial response support
|
||||
fn supports_initial_response() -> bool;
|
||||
/// Returns the response
|
||||
fn response(username: &str, password: &str, challenge: Option<&str>) -> Result<String, Error>;
|
||||
/// TODO
|
||||
#[derive(PartialEq,Eq,Copy,Clone,Hash,Debug)]
|
||||
pub enum Mecanism {
|
||||
/// PLAIN authentication mecanism
|
||||
/// RFC 4616: https://tools.ietf.org/html/rfc4616
|
||||
Plain,
|
||||
/// CRAM-MD5 authentication mecanism
|
||||
/// RFC 2195: https://tools.ietf.org/html/rfc2195
|
||||
CramMd5,
|
||||
}
|
||||
|
||||
/// PLAIN authentication mecanism
|
||||
/// RFC 4616: https://tools.ietf.org/html/rfc4616
|
||||
#[derive(Copy,Clone)]
|
||||
pub struct Plain;
|
||||
|
||||
impl Mecanism for Plain {
|
||||
fn extension() -> Extension {
|
||||
Extension::PlainAuthentication
|
||||
impl Display for Mecanism {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
write!(f, "{}",
|
||||
match *self {
|
||||
Mecanism::Plain => "PLAIN",
|
||||
Mecanism::CramMd5 => "CRAM-MD5",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn supports_initial_response() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn response(username: &str, password: &str, challenge: Option<&str>) -> Result<String, Error> {
|
||||
match challenge {
|
||||
Some(_) => Err(Error::ClientError("This mecanism does not expect a challenge")),
|
||||
None => Ok(format!("{}{}{}{}", NUL, username, NUL, password).as_bytes().to_base64(base64::STANDARD)),
|
||||
impl Mecanism {
|
||||
/// TODO
|
||||
pub fn supports_initial_response(&self) -> bool {
|
||||
match *self {
|
||||
Mecanism::Plain => true,
|
||||
Mecanism::CramMd5 => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CRAM-MD5 authentication mecanism
|
||||
/// RFC 2195: https://tools.ietf.org/html/rfc2195
|
||||
#[derive(Copy,Clone)]
|
||||
pub struct CramMd5;
|
||||
/// TODO
|
||||
pub fn response(&self, username: &str, password: &str, challenge: Option<&str>) -> Result<String, Error> {
|
||||
match *self {
|
||||
Mecanism::Plain => {
|
||||
match challenge {
|
||||
Some(_) => Err(Error::ClientError("This mecanism does not expect a challenge")),
|
||||
None => Ok(format!("{}{}{}{}", NUL, username, NUL, password).as_bytes().to_base64(base64::STANDARD)),
|
||||
}
|
||||
},
|
||||
Mecanism::CramMd5 => {
|
||||
let encoded_challenge = match challenge {
|
||||
Some(challenge) => challenge,
|
||||
None => return Err(Error::ClientError("This mecanism does expect a challenge")),
|
||||
};
|
||||
|
||||
impl Mecanism for CramMd5 {
|
||||
fn extension() -> Extension {
|
||||
Extension::CramMd5Authentication
|
||||
}
|
||||
let decoded_challenge = match encoded_challenge.from_base64() {
|
||||
Ok(challenge) => challenge,
|
||||
Err(error) => return Err(Error::ChallengeParsingError(error)),
|
||||
};
|
||||
|
||||
fn supports_initial_response() -> bool {
|
||||
false
|
||||
}
|
||||
let mut hmac = Hmac::new(Md5::new(), password.as_bytes());
|
||||
hmac.input(&decoded_challenge);
|
||||
|
||||
fn response(username: &str, password: &str, challenge: Option<&str>) -> Result<String, Error> {
|
||||
let encoded_challenge = match challenge {
|
||||
Some(challenge) => challenge,
|
||||
None => return Err(Error::ClientError("This mecanism does expect a challenge")),
|
||||
};
|
||||
|
||||
let decoded_challenge = match encoded_challenge.from_base64() {
|
||||
Ok(challenge) => challenge,
|
||||
Err(error) => return Err(Error::ChallengeParsingError(error)),
|
||||
};
|
||||
|
||||
let mut hmac = Hmac::new(Md5::new(), password.as_bytes());
|
||||
hmac.input(&decoded_challenge);
|
||||
|
||||
Ok(format!("{} {}", username, hmac.result().code().to_hex()).as_bytes().to_base64(base64::STANDARD))
|
||||
Ok(format!("{} {}", username, hmac.result().code().to_hex()).as_bytes().to_base64(base64::STANDARD))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{Mecanism, Plain, CramMd5};
|
||||
use super::Mecanism;
|
||||
|
||||
#[test]
|
||||
fn test_plain() {
|
||||
assert_eq!(Plain::response("username", "password", None).unwrap(), "AHVzZXJuYW1lAHBhc3N3b3Jk");
|
||||
let mecanism = Mecanism::Plain;
|
||||
|
||||
assert_eq!(mecanism.response("username", "password", None).unwrap(), "AHVzZXJuYW1lAHBhc3N3b3Jk");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cram_md5() {
|
||||
assert_eq!(CramMd5::response("alice", "wonderland",
|
||||
let mecanism = Mecanism::CramMd5;
|
||||
|
||||
assert_eq!(mecanism.response("alice", "wonderland",
|
||||
Some("PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg==")).unwrap(),
|
||||
"YWxpY2UgNjRiMmE0M2MxZjZlZDY4MDZhOTgwOTE0ZTIzZTc1ZjA=");
|
||||
}
|
||||
|
||||
54
src/client/authentication.rs
Normal file
54
src/client/authentication.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2014 Alexis Mousset. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! Provides authentication functions
|
||||
|
||||
use serialize::base64::{self, ToBase64, FromBase64};
|
||||
use serialize::hex::ToHex;
|
||||
use crypto::hmac::Hmac;
|
||||
use crypto::md5::Md5;
|
||||
use crypto::mac::Mac;
|
||||
|
||||
use NUL;
|
||||
use error::Error;
|
||||
|
||||
/// Returns a PLAIN mecanism response
|
||||
pub fn plain(username: &str, password: &str) -> String {
|
||||
format!("{}{}{}{}", NUL, username, NUL, password).as_bytes().to_base64(base64::STANDARD)
|
||||
}
|
||||
|
||||
/// Returns a CRAM-MD5 mecanism response
|
||||
pub fn cram_md5(username: &str, password: &str, encoded_challenge: &str) -> Result<String, Error> {
|
||||
let challenge = match encoded_challenge.from_base64() {
|
||||
Ok(challenge) => challenge,
|
||||
Err(error) => return Err(Error::ChallengeParsingError(error)),
|
||||
};
|
||||
|
||||
let mut hmac = Hmac::new(Md5::new(), password.as_bytes());
|
||||
hmac.input(&challenge);
|
||||
|
||||
Ok(format!("{} {}", username, hmac.result().code().to_hex()).as_bytes().to_base64(base64::STANDARD))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{plain, cram_md5};
|
||||
|
||||
#[test]
|
||||
fn test_plain() {
|
||||
assert_eq!(plain("username", "password"), "AHVzZXJuYW1lAHBhc3N3b3Jk");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cram_md5() {
|
||||
assert_eq!(cram_md5("alice", "wonderland",
|
||||
"PDE3ODkzLjEzMjA2NzkxMjNAdGVzc2VyYWN0LnN1c2FtLmluPg==").unwrap(),
|
||||
"YWxpY2UgNjRiMmE0M2MxZjZlZDY4MDZhOTgwOTE0ZTIzZTc1ZjA=");
|
||||
}
|
||||
}
|
||||
0
src/client/connector.rs
Normal file
0
src/client/connector.rs
Normal file
@@ -16,7 +16,7 @@ use std::io::{BufRead, Read, Write};
|
||||
use bufstream::BufStream;
|
||||
|
||||
use response::ResponseParser;
|
||||
use authentication::{Mecanism, CramMd5, Plain};
|
||||
use authentication::Mecanism;
|
||||
use error::{Error, SmtpResult};
|
||||
use client::net::{Connector, SmtpStream};
|
||||
use {CRLF, MESSAGE_ENDING};
|
||||
@@ -167,21 +167,21 @@ impl<S: Connector + Write + Read = SmtpStream> Client<S> {
|
||||
self.command("RSET")
|
||||
}
|
||||
|
||||
/// Sends an AUTH command with PLAIN mecanism
|
||||
pub fn auth_plain(&mut self, username: &str, password: &str) -> SmtpResult {
|
||||
self.command(&format!("AUTH PLAIN {}", try!(Plain::response(username, password, None))))
|
||||
}
|
||||
/// Sends an AUTH command with the given mecanism
|
||||
pub fn auth(&mut self, mecanism: Mecanism, username: &str, password: &str) -> SmtpResult {
|
||||
|
||||
/// Sends an AUTH command with CRAM-MD5 mecanism
|
||||
pub fn auth_cram_md5(&mut self, username: &str, password: &str) -> SmtpResult {
|
||||
let encoded_challenge = match try!(self.command("AUTH CRAM-MD5")).first_word() {
|
||||
Some(challenge) => challenge,
|
||||
None => return Err(Error::ResponseParsingError("Could not read CRAM challenge")),
|
||||
};
|
||||
if mecanism.supports_initial_response() {
|
||||
self.command(&format!("AUTH {} {}", mecanism, try!(mecanism.response(username, password, None))))
|
||||
} else {
|
||||
let encoded_challenge = match try!(self.command("AUTH CRAM-MD5")).first_word() {
|
||||
Some(challenge) => challenge,
|
||||
None => return Err(Error::ResponseParsingError("Could not read CRAM challenge")),
|
||||
};
|
||||
|
||||
let cram_response = try!(CramMd5::response(username, password, Some(&encoded_challenge)));
|
||||
let cram_response = try!(mecanism.response(username, password, Some(&encoded_challenge)));
|
||||
|
||||
self.command(&format!("AUTH CRAM-MD5 {}", cram_response))
|
||||
self.command(&format!("AUTH CRAM-MD5 {}", cram_response))
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends the message content
|
||||
|
||||
169
src/extension.rs
169
src/extension.rs
@@ -9,14 +9,17 @@
|
||||
|
||||
//! ESMTP features
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::result::Result;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fmt::Result as FmtResult;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use response::Response;
|
||||
use self::Extension::*;
|
||||
use error::Error;
|
||||
use authentication::Mecanism;
|
||||
|
||||
/// Supported ESMTP keywords
|
||||
#[derive(PartialEq,Eq,Copy,Clone,Debug)]
|
||||
#[derive(PartialEq,Eq,Hash,Clone,Debug)]
|
||||
pub enum Extension {
|
||||
/// 8BITMIME keyword
|
||||
///
|
||||
@@ -30,85 +33,115 @@ pub enum Extension {
|
||||
///
|
||||
/// RFC 2487: https://tools.ietf.org/html/rfc2487
|
||||
StartTls,
|
||||
/// AUTH PLAIN mecanism
|
||||
///
|
||||
/// RFC 4616: https://tools.ietf.org/html/rfc4616
|
||||
PlainAuthentication,
|
||||
/// AUTH CRAM-MD5 mecanism
|
||||
///
|
||||
/// RFC 2195: https://tools.ietf.org/html/rfc2195
|
||||
CramMd5Authentication,
|
||||
/// AUTH mecanism
|
||||
Authentication(Mecanism),
|
||||
}
|
||||
|
||||
impl Extension {
|
||||
fn from_str(s: &str) -> Result<Vec<Extension>, &'static str> {
|
||||
let splitted : Vec<&str> = s.split_whitespace().collect();
|
||||
match (splitted[0], splitted.len()) {
|
||||
("8BITMIME", 1) => Ok(vec![EightBitMime]),
|
||||
("SMTPUTF8", 1) => Ok(vec![SmtpUtfEight]),
|
||||
("STARTTLS", 1) => Ok(vec![StartTls]),
|
||||
("AUTH", _) => {
|
||||
let mut mecanisms: Vec<Extension> = vec![];
|
||||
for &mecanism in &splitted[1..] {
|
||||
match mecanism {
|
||||
"PLAIN" => mecanisms.push(PlainAuthentication),
|
||||
"CRAM-MD5" => mecanisms.push(CramMd5Authentication),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Ok(mecanisms)
|
||||
},
|
||||
_ => Err("Unknown extension"),
|
||||
}
|
||||
impl Display for Extension {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
write!(f, "{}",
|
||||
match *self {
|
||||
Extension::EightBitMime => "8BITMIME",
|
||||
Extension::SmtpUtfEight => "SMTPUTF8",
|
||||
Extension::StartTls => "STARTTLS",
|
||||
Extension::Authentication(_) => "AUTH",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses supported ESMTP features
|
||||
pub fn parse_esmtp_response(response: &Response) -> Vec<Extension> {
|
||||
let mut esmtp_features: Vec<Extension> = Vec::new();
|
||||
/// Contains information about an SMTP server
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ServerInfo {
|
||||
/// Server name
|
||||
///
|
||||
/// The name given in the server banner
|
||||
pub name: String,
|
||||
/// ESMTP features supported by the server
|
||||
///
|
||||
/// It contains the features supported by the server and known by the `Extension` module.
|
||||
pub esmtp_features: HashSet<Extension>,
|
||||
}
|
||||
|
||||
impl Display for ServerInfo {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
write!(f, "{} with {}",
|
||||
self.name,
|
||||
match self.esmtp_features.is_empty() {
|
||||
true => "no supported features".to_string(),
|
||||
false => format! ("{:?}", self.esmtp_features),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerInfo {
|
||||
/// Parses a response to create a `ServerInfo`
|
||||
pub fn from_response(response: &Response) -> Result<ServerInfo, Error> {
|
||||
let name = match response.first_word() {
|
||||
Some(name) => name,
|
||||
None => return Err(Error::ResponseParsingError("Could not read server name"))
|
||||
};
|
||||
|
||||
let mut esmtp_features: HashSet<Extension> = HashSet::new();
|
||||
|
||||
for line in response.message() {
|
||||
if let Ok(keywords) = Extension::from_str(&line) {
|
||||
for keyword in keywords {
|
||||
esmtp_features.push(keyword);
|
||||
}
|
||||
|
||||
let splitted : Vec<&str> = line.split_whitespace().collect();
|
||||
let _ = match (splitted[0], splitted.len()) {
|
||||
("8BITMIME", 1) => {esmtp_features.insert(Extension::EightBitMime);},
|
||||
("SMTPUTF8", 1) => {esmtp_features.insert(Extension::SmtpUtfEight);},
|
||||
("STARTTLS", 1) => {esmtp_features.insert(Extension::StartTls);},
|
||||
("AUTH", _) => {
|
||||
for &mecanism in &splitted[1..] {
|
||||
match mecanism {
|
||||
"PLAIN" => {esmtp_features.insert(Extension::Authentication(Mecanism::Plain));},
|
||||
"CRAM-MD5" => {esmtp_features.insert(Extension::Authentication(Mecanism::CramMd5));},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
},
|
||||
(_, _) => (),
|
||||
};
|
||||
}
|
||||
|
||||
esmtp_features
|
||||
Ok(ServerInfo{
|
||||
name: name,
|
||||
esmtp_features: esmtp_features,
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks if the server supports an ESMTP feature
|
||||
pub fn supports_feature(&self, keyword: &Extension) -> bool {
|
||||
self.esmtp_features.contains(keyword)
|
||||
}
|
||||
|
||||
/// Checks if the server supports an ESMTP feature
|
||||
pub fn supports_auth_mecanism(&self, mecanism: Mecanism) -> bool {
|
||||
self.esmtp_features.contains(&Extension::Authentication(mecanism))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Extension;
|
||||
use response::{Severity, Category, Response, Code};
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use super::{ServerInfo, Extension};
|
||||
|
||||
#[test]
|
||||
fn test_from_str() {
|
||||
assert_eq!(Extension::from_str("8BITMIME"), Ok(vec![Extension::EightBitMime]));
|
||||
assert_eq!(Extension::from_str("AUTH PLAIN"), Ok(vec![Extension::PlainAuthentication]));
|
||||
assert_eq!(Extension::from_str("AUTH PLAIN LOGIN CRAM-MD5"), Ok(vec![Extension::PlainAuthentication, Extension::CramMd5Authentication]));
|
||||
assert_eq!(Extension::from_str("AUTH CRAM-MD5 PLAIN"), Ok(vec![Extension::CramMd5Authentication, Extension::PlainAuthentication]));
|
||||
assert_eq!(Extension::from_str("AUTH DIGEST-MD5 PLAIN CRAM-MD5"), Ok(vec![Extension::PlainAuthentication, Extension::CramMd5Authentication]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_esmtp_response() {
|
||||
assert_eq!(Extension::parse_esmtp_response(&Response::new(
|
||||
Code::new(
|
||||
"2".parse::<Severity>().unwrap(),
|
||||
"2".parse::<Category>().unwrap(),
|
||||
1,
|
||||
),
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "SIZE 42".to_string()]
|
||||
)), vec![Extension::EightBitMime]);
|
||||
assert_eq!(Extension::parse_esmtp_response(&Response::new(
|
||||
Code::new(
|
||||
"4".parse::<Severity>().unwrap(),
|
||||
"3".parse::<Category>().unwrap(),
|
||||
3,
|
||||
),
|
||||
vec!["me".to_string(), "8BITMIME".to_string(), "AUTH PLAIN CRAM-MD5".to_string()]
|
||||
)), vec![Extension::EightBitMime, Extension::PlainAuthentication, Extension::CramMd5Authentication]);
|
||||
fn test_serverinfo_fmt() {
|
||||
let mut eightbitmime = HashSet::new();
|
||||
assert!(eightbitmime.insert(Extension::EightBitMime));
|
||||
|
||||
let empty = HashSet::new();
|
||||
|
||||
assert_eq!(format!("{}", ServerInfo{
|
||||
name: "name".to_string(),
|
||||
esmtp_features: eightbitmime.clone()
|
||||
}), "name with {EightBitMime}".to_string());
|
||||
assert_eq!(format!("{}", ServerInfo{
|
||||
name: "name".to_string(),
|
||||
esmtp_features: empty,
|
||||
}), "name with no supported features".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,12 @@ use std::string::String;
|
||||
use std::net::{SocketAddr, ToSocketAddrs};
|
||||
|
||||
use SMTP_PORT;
|
||||
use extension::Extension;
|
||||
use extension::{Extension, ServerInfo};
|
||||
use error::{SmtpResult, Error};
|
||||
use sendable_email::SendableEmail;
|
||||
use sender::server_info::ServerInfo;
|
||||
use client::Client;
|
||||
use client::net::SmtpStream;
|
||||
|
||||
mod server_info;
|
||||
use authentication::Mecanism;
|
||||
|
||||
/// Contains client configuration
|
||||
pub struct SenderBuilder {
|
||||
@@ -171,37 +169,26 @@ impl Sender {
|
||||
if self.state.connection_reuse_count == 0 {
|
||||
try!(self.client.connect());
|
||||
|
||||
let hello_error = Error::ResponseParsingError("No hostname announced by the server");
|
||||
|
||||
// Log the connection
|
||||
info!("connection established to {}", self.client_info.server_addr);
|
||||
|
||||
// Extended Hello or Hello if needed
|
||||
match self.client.ehlo(&self.client_info.hello_name) {
|
||||
Ok(response) => {self.server_info = Some(
|
||||
ServerInfo{
|
||||
name: try_smtp!(response.first_word().ok_or(hello_error), self),
|
||||
esmtp_features: Extension::parse_esmtp_response(&response),
|
||||
});
|
||||
},
|
||||
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) => {self.server_info = Some(
|
||||
ServerInfo{
|
||||
name: try_smtp!(response.first_word().ok_or(hello_error), self),
|
||||
esmtp_features: vec!(),
|
||||
});
|
||||
},
|
||||
Ok(response) => response,
|
||||
Err(error) => try_smtp!(Err(error), self)
|
||||
}
|
||||
|
||||
},
|
||||
_ => {
|
||||
try_smtp!(Err(error), self)
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
self.server_info = Some(try_smtp!(ServerInfo::from_response(&hello_response), self));
|
||||
|
||||
// Print server information
|
||||
debug!("server {}", self.server_info.as_ref().unwrap());
|
||||
@@ -212,11 +199,11 @@ impl Sender {
|
||||
|
||||
let (username, password) = self.client_info.credentials.clone().unwrap();
|
||||
|
||||
if self.server_info.as_ref().unwrap().supports_feature(Extension::CramMd5Authentication) {
|
||||
let result = self.client.auth_cram_md5(&username, &password);
|
||||
if self.server_info.as_ref().unwrap().supports_auth_mecanism(Mecanism::CramMd5) {
|
||||
let result = self.client.auth(Mecanism::CramMd5, &username, &password);
|
||||
try_smtp!(result, self);
|
||||
} else if self.server_info.as_ref().unwrap().supports_feature(Extension::PlainAuthentication) {
|
||||
let result = self.client.auth_plain(&username, &password);
|
||||
} else if self.server_info.as_ref().unwrap().supports_auth_mecanism(Mecanism::Plain) {
|
||||
let result = self.client.auth(Mecanism::Plain, &username, &password);
|
||||
try_smtp!(result, self);
|
||||
} else {
|
||||
debug!("No supported authentication mecanisms available");
|
||||
@@ -229,7 +216,7 @@ impl Sender {
|
||||
let message = email.message();
|
||||
|
||||
// Mail
|
||||
let mail_options = match self.server_info.as_ref().unwrap().supports_feature(Extension::EightBitMime) {
|
||||
let mail_options = match self.server_info.as_ref().unwrap().supports_feature(&Extension::EightBitMime) {
|
||||
true => Some("BODY=8BITMIME"),
|
||||
false => None,
|
||||
};
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright 2014 Alexis Mousset. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//! Information about a server
|
||||
|
||||
use std::fmt;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use extension::Extension;
|
||||
|
||||
/// Contains information about an SMTP server
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct ServerInfo {
|
||||
/// Server name
|
||||
///
|
||||
/// The name given in the server banner
|
||||
pub name: String,
|
||||
/// ESMTP features supported by the server
|
||||
///
|
||||
/// It contains the features supported by the server and known by the `Extension` module.
|
||||
pub esmtp_features: Vec<Extension>,
|
||||
}
|
||||
|
||||
impl Display for ServerInfo {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{} with {}",
|
||||
self.name,
|
||||
match self.esmtp_features.is_empty() {
|
||||
true => "no supported features".to_string(),
|
||||
false => format! ("{:?}", self.esmtp_features),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerInfo {
|
||||
/// Checks if the server supports an ESMTP feature
|
||||
pub fn supports_feature(&self, keyword: Extension) -> bool {
|
||||
self.esmtp_features.contains(&keyword)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::ServerInfo;
|
||||
use extension::Extension;
|
||||
|
||||
#[test]
|
||||
fn test_fmt() {
|
||||
assert_eq!(format!("{}", ServerInfo{
|
||||
name: "name".to_string(),
|
||||
esmtp_features: vec![Extension::EightBitMime]
|
||||
}), "name with [EightBitMime]".to_string());
|
||||
assert_eq!(format!("{}", ServerInfo{
|
||||
name: "name".to_string(),
|
||||
esmtp_features: vec![Extension::EightBitMime]
|
||||
}), "name with [EightBitMime]".to_string());
|
||||
assert_eq!(format!("{}", ServerInfo{
|
||||
name: "name".to_string(),
|
||||
esmtp_features: vec![]
|
||||
}), "name with no supported features".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_supports_feature() {
|
||||
assert!(ServerInfo{
|
||||
name: "name".to_string(),
|
||||
esmtp_features: vec![Extension::EightBitMime]
|
||||
}.supports_feature(Extension::EightBitMime));
|
||||
assert!(ServerInfo{
|
||||
name: "name".to_string(),
|
||||
esmtp_features: vec![Extension::PlainAuthentication, Extension::EightBitMime]
|
||||
}.supports_feature(Extension::EightBitMime));
|
||||
assert_eq!(ServerInfo{
|
||||
name: "name".to_string(),
|
||||
esmtp_features: vec![Extension::EightBitMime]
|
||||
}.supports_feature(Extension::PlainAuthentication), false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user