feat(transport): Initial support for XOAUTH2
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
//! Provides limited SASL authentication mechanisms
|
||||
|
||||
use smtp::NUL;
|
||||
use smtp::error::Error;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
@@ -56,6 +55,9 @@ pub enum Mechanism {
|
||||
/// Obsolete but needed for some providers (like office365)
|
||||
/// https://www.ietf.org/archive/id/draft-murchison-sasl-login-00.txt
|
||||
Login,
|
||||
/// Non-standard XOAUTH2 mechanism
|
||||
/// https://developers.google.com/gmail/imap/xoauth2-protocol
|
||||
Xoauth2,
|
||||
}
|
||||
|
||||
impl Display for Mechanism {
|
||||
@@ -66,6 +68,7 @@ impl Display for Mechanism {
|
||||
match *self {
|
||||
Mechanism::Plain => "PLAIN",
|
||||
Mechanism::Login => "LOGIN",
|
||||
Mechanism::Xoauth2 => "XOAUTH2",
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -73,10 +76,9 @@ 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::Plain | Mechanism::Xoauth2 => true,
|
||||
Mechanism::Login => false,
|
||||
}
|
||||
}
|
||||
@@ -92,8 +94,8 @@ impl Mechanism {
|
||||
Mechanism::Plain => match challenge {
|
||||
Some(_) => Err(Error::Client("This mechanism does not expect a challenge")),
|
||||
None => Ok(format!(
|
||||
"{}{}{}{}",
|
||||
NUL, credentials.authentication_identity, NUL, credentials.secret
|
||||
"\u{0}{}\u{0}{}",
|
||||
credentials.authentication_identity, credentials.secret
|
||||
)),
|
||||
},
|
||||
Mechanism::Login => {
|
||||
@@ -111,7 +113,14 @@ impl Mechanism {
|
||||
}
|
||||
|
||||
Err(Error::Client("Unrecognized challenge"))
|
||||
}
|
||||
},
|
||||
Mechanism::Xoauth2 => match challenge {
|
||||
Some(_) => Err(Error::Client("This mechanism does not expect a challenge")),
|
||||
None => Ok(format!(
|
||||
"user={}\x01auth=Bearer {}\x01\x01",
|
||||
credentials.authentication_identity, credentials.secret
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,4 +158,17 @@ mod test {
|
||||
);
|
||||
assert!(mechanism.response(&credentials, None).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_xoauth2() {
|
||||
let mechanism = Mechanism::Xoauth2;
|
||||
|
||||
let credentials = Credentials::new("username".to_string(), "vF9dft4qmTc2Nvb3RlckBhdHRhdmlzdGEuY29tCg==".to_string());
|
||||
|
||||
assert_eq!(
|
||||
mechanism.response(&credentials, None).unwrap(),
|
||||
"user=username\x01auth=Bearer vF9dft4qmTc2Nvb3RlckBhdHRhdmlzdGEuY29tCg==\x01\x01"
|
||||
);
|
||||
assert!(mechanism.response(&credentials, Some("test")).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
use bufstream::BufStream;
|
||||
use nom::ErrorKind as NomErrorKind;
|
||||
use smtp::{CRLF, MESSAGE_ENDING};
|
||||
use smtp::authentication::{Credentials, Mechanism};
|
||||
use smtp::client::net::{ClientTlsParameters, Connector, NetworkStream, Timeout};
|
||||
use smtp::commands::*;
|
||||
@@ -72,7 +71,7 @@ impl ClientCodec {
|
||||
/// Returns the string replacing all the CRLF with "\<CRLF\>"
|
||||
/// Used for debug displays
|
||||
fn escape_crlf(string: &str) -> String {
|
||||
string.replace(CRLF, "<CRLF>")
|
||||
string.replace("\r\n", "<CRLF>")
|
||||
}
|
||||
|
||||
/// Structure that implements the SMTP client
|
||||
@@ -219,7 +218,7 @@ impl<S: Connector + Write + Read + Timeout + Debug> InnerClient<S> {
|
||||
self.write(out_buf.as_slice())?;
|
||||
}
|
||||
|
||||
self.write(MESSAGE_ENDING.as_bytes())?;
|
||||
self.write("\r\n.\r\n".as_bytes())?;
|
||||
self.read_response()
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
use EmailAddress;
|
||||
use base64;
|
||||
use smtp::CRLF;
|
||||
use smtp::authentication::{Credentials, Mechanism};
|
||||
use smtp::error::Error;
|
||||
use smtp::extension::{MailParameter, RcptParameter};
|
||||
@@ -19,8 +18,7 @@ pub struct EhloCommand {
|
||||
|
||||
impl Display for EhloCommand {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "EHLO {}", self.client_id)?;
|
||||
f.write_str(CRLF)
|
||||
write!(f, "EHLO {}\r\n", self.client_id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +36,7 @@ pub struct StarttlsCommand;
|
||||
|
||||
impl Display for StarttlsCommand {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str("STARTTLS")?;
|
||||
f.write_str(CRLF)
|
||||
f.write_str("STARTTLS\r\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +61,7 @@ impl Display for MailCommand {
|
||||
for parameter in &self.parameters {
|
||||
write!(f, " {}", parameter)?;
|
||||
}
|
||||
f.write_str(CRLF)
|
||||
f.write_str("\r\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +86,7 @@ impl Display for RcptCommand {
|
||||
for parameter in &self.parameters {
|
||||
write!(f, " {}", parameter)?;
|
||||
}
|
||||
f.write_str(CRLF)
|
||||
f.write_str("\r\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,8 +107,7 @@ pub struct DataCommand;
|
||||
|
||||
impl Display for DataCommand {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str("DATA")?;
|
||||
f.write_str(CRLF)
|
||||
f.write_str("DATA\r\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,8 +118,7 @@ pub struct QuitCommand;
|
||||
|
||||
impl Display for QuitCommand {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str("QUIT")?;
|
||||
f.write_str(CRLF)
|
||||
f.write_str("QUIT\r\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,8 +129,7 @@ pub struct NoopCommand;
|
||||
|
||||
impl Display for NoopCommand {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str("NOOP")?;
|
||||
f.write_str(CRLF)
|
||||
f.write_str("NOOP\r\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +146,7 @@ impl Display for HelpCommand {
|
||||
if self.argument.is_some() {
|
||||
write!(f, " {}", self.argument.as_ref().unwrap())?;
|
||||
}
|
||||
f.write_str(CRLF)
|
||||
f.write_str("\r\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,8 +166,7 @@ pub struct VrfyCommand {
|
||||
|
||||
impl Display for VrfyCommand {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "VRFY {}", self.argument)?;
|
||||
f.write_str(CRLF)
|
||||
write!(f, "VRFY {}\r\n", self.argument)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,8 +186,7 @@ pub struct ExpnCommand {
|
||||
|
||||
impl Display for ExpnCommand {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "EXPN {}", self.argument)?;
|
||||
f.write_str(CRLF)
|
||||
write!(f, "EXPN {}\r\n", self.argument)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,8 +204,7 @@ pub struct RsetCommand;
|
||||
|
||||
impl Display for RsetCommand {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str("RSET")?;
|
||||
f.write_str(CRLF)
|
||||
f.write_str("RSET\r\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,7 +237,7 @@ impl Display for AuthCommand {
|
||||
None => write!(f, "AUTH {}", self.mechanism)?,
|
||||
}
|
||||
}
|
||||
f.write_str(CRLF)
|
||||
f.write_str("\r\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -145,6 +145,9 @@ impl ServerInfo {
|
||||
"LOGIN" => {
|
||||
features.insert(Extension::Authentication(Mechanism::Login));
|
||||
}
|
||||
"XOAUTH2" => {
|
||||
features.insert(Extension::Authentication(Mechanism::Xoauth2));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
},
|
||||
@@ -362,7 +365,7 @@ mod test {
|
||||
),
|
||||
vec![
|
||||
"me".to_string(),
|
||||
"AUTH PLAIN CRAM-MD5 OTHER".to_string(),
|
||||
"AUTH PLAIN CRAM-MD5 XOAUTH2 OTHER".to_string(),
|
||||
"8BITMIME".to_string(),
|
||||
"SIZE 42".to_string(),
|
||||
],
|
||||
@@ -371,6 +374,7 @@ 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::Xoauth2),));
|
||||
|
||||
let server_info2 = ServerInfo {
|
||||
name: "me".to_string(),
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
//! It implements the following extensions:
|
||||
//!
|
||||
//! * 8BITMIME ([RFC 6152](https://tools.ietf.org/html/rfc6152))
|
||||
//! * AUTH ([RFC 4954](http://tools.ietf.org/html/rfc4954)) with PLAIN, LOGIN mechanisms
|
||||
//! * AUTH ([RFC 4954](http://tools.ietf.org/html/rfc4954)) with PLAIN, LOGIN and XOAUTH2 mechanisms
|
||||
//! * STARTTLS ([RFC 2487](http://tools.ietf.org/html/rfc2487))
|
||||
//! * SMTPUTF8 ([RFC 6531](http://tools.ietf.org/html/rfc6531))
|
||||
//!
|
||||
@@ -44,23 +44,6 @@ pub const SMTP_PORT: u16 = 25;
|
||||
/// Default submission port
|
||||
pub const SUBMISSION_PORT: u16 = 587;
|
||||
|
||||
// Useful strings and characters
|
||||
|
||||
/// The word separator for SMTP transactions
|
||||
pub const SP: &str = " ";
|
||||
|
||||
/// The line ending for SMTP transactions (carriage return + line feed)
|
||||
pub const CRLF: &str = "\r\n";
|
||||
|
||||
/// Colon
|
||||
pub const COLON: &str = ":";
|
||||
|
||||
/// The ending of message content
|
||||
pub const MESSAGE_ENDING: &str = "\r\n.\r\n";
|
||||
|
||||
/// NUL unicode character
|
||||
pub const NUL: &str = "\0";
|
||||
|
||||
/// How to apply TLS to a client connection
|
||||
#[derive(Clone)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
|
||||
Reference in New Issue
Block a user