From 93d032181d808bf772d04a0439f5ae8f2bf35589 Mon Sep 17 00:00:00 2001 From: Kim Minh Kaplan Date: Wed, 7 Nov 2018 10:48:53 +0000 Subject: [PATCH 1/6] imap::client::Client::authenticate: Base64 encode the result of the Authenticator. Fixes issue #95. --- Cargo.toml | 2 -- src/client.rs | 25 ++++++++++++++++++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 33cae10..9f4ba18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,4 @@ regex = "1.0" bufstream = "0.1" imap-proto = "0.6" nom = "4.0" - -[dev-dependencies] base64 = "0.10" diff --git a/src/client.rs b/src/client.rs index dad95d4..8c1faf0 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,3 +1,4 @@ +extern crate base64; use bufstream::BufStream; use native_tls::{TlsConnector, TlsStream}; use nom; @@ -394,7 +395,7 @@ impl Client { parse_authenticate_response(String::from_utf8(line).unwrap()), self ); - let auth_response = authenticator.process(data); + let auth_response = base64::encode(authenticator.process(data).as_str()); ok_or_unauth_client_err!( self.write_line(auth_response.into_bytes().as_slice()), @@ -937,6 +938,28 @@ mod tests { ); } + #[test] + fn authenticate() { + let response = b"+\r\n\ + a1 OK Logged in\r\n".to_vec(); + let command = "a1 AUTHENTICATE PLAIN\r\n\ + Zm9v\r\n"; + let mock_stream = MockStream::new(response); + let client = Client::new(mock_stream); + enum Authenticate { Auth }; + impl Authenticator for Authenticate { + fn process(&self, _: String) -> String { + "foo".to_string() + } + } + let auth = Authenticate::Auth; + let session = client.authenticate("PLAIN", auth).unwrap(); + assert!( + session.stream.get_ref().written_buf == command.as_bytes().to_vec(), + "Invalid authenticate command" + ); + } + #[test] fn login() { let response = b"a1 OK Logged in\r\n".to_vec(); From 1dd55ff066062eeb6826965b389a975a28b3e796 Mon Sep 17 00:00:00 2001 From: Kim Minh Kaplan Date: Fri, 9 Nov 2018 18:35:07 +0000 Subject: [PATCH 2/6] Move Authenticator to returning an AsRef. --- examples/gmail_oauth2.rs | 12 +++++------- src/authenticator.rs | 3 ++- src/client.rs | 9 +++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/gmail_oauth2.rs b/examples/gmail_oauth2.rs index 051c7e2..50805f1 100644 --- a/examples/gmail_oauth2.rs +++ b/examples/gmail_oauth2.rs @@ -2,7 +2,6 @@ extern crate base64; extern crate imap; extern crate native_tls; -use base64::encode; use imap::authenticator::Authenticator; use native_tls::TlsConnector; @@ -12,13 +11,12 @@ struct GmailOAuth2 { } impl Authenticator for GmailOAuth2 { + type Response = String; #[allow(unused_variables)] - fn process(&self, data: String) -> String { - encode( - format!( - "user={}\x01auth=Bearer {}\x01\x01", - self.user, self.access_token - ).as_bytes(), + fn process(&self, data: String) -> Self::Response { + format!( + "user={}\x01auth=Bearer {}\x01\x01", + self.user, self.access_token ) } } diff --git a/src/authenticator.rs b/src/authenticator.rs index 825ec9a..0926924 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -1,4 +1,5 @@ /// This will allow plugable authentication mechanisms. pub trait Authenticator { - fn process(&self, String) -> String; + type Response: AsRef<[u8]>; + fn process(&self, String) -> Self::Response; } diff --git a/src/client.rs b/src/client.rs index 8c1faf0..734d0e9 100644 --- a/src/client.rs +++ b/src/client.rs @@ -395,8 +395,8 @@ impl Client { parse_authenticate_response(String::from_utf8(line).unwrap()), self ); - let auth_response = base64::encode(authenticator.process(data).as_str()); - + let raw_response = &authenticator.process(data); + let auth_response = base64::encode(raw_response); ok_or_unauth_client_err!( self.write_line(auth_response.into_bytes().as_slice()), self @@ -948,8 +948,9 @@ mod tests { let client = Client::new(mock_stream); enum Authenticate { Auth }; impl Authenticator for Authenticate { - fn process(&self, _: String) -> String { - "foo".to_string() + type Response = Vec; + fn process(&self, _: String) -> Self::Response { + b"foo".to_vec() } } let auth = Authenticate::Auth; From 892fe49a68ac04de373480c6c91be31102cd41b3 Mon Sep 17 00:00:00 2001 From: Kim Minh Kaplan Date: Fri, 9 Nov 2018 22:33:05 +0000 Subject: [PATCH 3/6] Decode the Base64 AUTHENTICATE challenge --- examples/gmail_oauth2.rs | 2 +- src/authenticator.rs | 2 +- src/client.rs | 17 ++++++++++++++--- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/examples/gmail_oauth2.rs b/examples/gmail_oauth2.rs index 50805f1..0fe212c 100644 --- a/examples/gmail_oauth2.rs +++ b/examples/gmail_oauth2.rs @@ -13,7 +13,7 @@ struct GmailOAuth2 { impl Authenticator for GmailOAuth2 { type Response = String; #[allow(unused_variables)] - fn process(&self, data: String) -> Self::Response { + fn process(&self, data: Vec) -> Self::Response { format!( "user={}\x01auth=Bearer {}\x01\x01", self.user, self.access_token diff --git a/src/authenticator.rs b/src/authenticator.rs index 0926924..1e5187f 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -1,5 +1,5 @@ /// This will allow plugable authentication mechanisms. pub trait Authenticator { type Response: AsRef<[u8]>; - fn process(&self, String) -> Self::Response; + fn process(&self, Vec) -> Self::Response; } diff --git a/src/client.rs b/src/client.rs index 734d0e9..017bd1d 100644 --- a/src/client.rs +++ b/src/client.rs @@ -395,7 +395,14 @@ impl Client { parse_authenticate_response(String::from_utf8(line).unwrap()), self ); - let raw_response = &authenticator.process(data); + let challenge = ok_or_unauth_client_err!( + base64::decode(data.as_str()) + .map_err(|_| + Error::Parse(ParseError::Authentication(data)) + ), + self + ); + let raw_response = &authenticator.process(challenge); let auth_response = base64::encode(raw_response); ok_or_unauth_client_err!( self.write_line(auth_response.into_bytes().as_slice()), @@ -940,7 +947,7 @@ mod tests { #[test] fn authenticate() { - let response = b"+\r\n\ + let response = b"+YmFy\r\n\ a1 OK Logged in\r\n".to_vec(); let command = "a1 AUTHENTICATE PLAIN\r\n\ Zm9v\r\n"; @@ -949,7 +956,11 @@ mod tests { enum Authenticate { Auth }; impl Authenticator for Authenticate { type Response = Vec; - fn process(&self, _: String) -> Self::Response { + fn process(&self, challenge: Vec) -> Self::Response { + assert!( + challenge == b"bar".to_vec(), + "Invalid authenticate challenge" + ); b"foo".to_vec() } } From 9e0a5d7c8a3759e3a256a4548063ee0a05f04a8c Mon Sep 17 00:00:00 2001 From: Kim Minh Kaplan Date: Fri, 9 Nov 2018 22:33:54 +0000 Subject: [PATCH 4/6] Escape '+' character in regexp. --- src/parse.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parse.rs b/src/parse.rs index 194b02a..f198b46 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -7,7 +7,7 @@ use super::error::{Error, ParseError, Result}; use super::types::*; pub fn parse_authenticate_response(line: String) -> Result { - let authenticate_regex = Regex::new("^+(.*)\r\n").unwrap(); + let authenticate_regex = Regex::new("^\\+(.*)\r\n").unwrap(); if let Some(cap) = authenticate_regex.captures_iter(line.as_str()).next() { let data = cap.get(1).map(|x| x.as_str()).unwrap_or(""); From c2c7e2a3f74e46ba25ec423da08d456dbadd98e1 Mon Sep 17 00:00:00 2001 From: Kim Minh Kaplan Date: Sat, 10 Nov 2018 08:54:46 +0000 Subject: [PATCH 5/6] In Authenticator::process change the challenge from a Vec to a &[u8] --- examples/gmail_oauth2.rs | 2 +- src/authenticator.rs | 2 +- src/client.rs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/gmail_oauth2.rs b/examples/gmail_oauth2.rs index 0fe212c..769be3d 100644 --- a/examples/gmail_oauth2.rs +++ b/examples/gmail_oauth2.rs @@ -13,7 +13,7 @@ struct GmailOAuth2 { impl Authenticator for GmailOAuth2 { type Response = String; #[allow(unused_variables)] - fn process(&self, data: Vec) -> Self::Response { + fn process(&self, data: &[u8]) -> Self::Response { format!( "user={}\x01auth=Bearer {}\x01\x01", self.user, self.access_token diff --git a/src/authenticator.rs b/src/authenticator.rs index 1e5187f..ecfe93d 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -1,5 +1,5 @@ /// This will allow plugable authentication mechanisms. pub trait Authenticator { type Response: AsRef<[u8]>; - fn process(&self, Vec) -> Self::Response; + fn process(&self, &[u8]) -> Self::Response; } diff --git a/src/client.rs b/src/client.rs index 017bd1d..cfdebb8 100644 --- a/src/client.rs +++ b/src/client.rs @@ -402,7 +402,7 @@ impl Client { ), self ); - let raw_response = &authenticator.process(challenge); + let raw_response = &authenticator.process(&challenge); let auth_response = base64::encode(raw_response); ok_or_unauth_client_err!( self.write_line(auth_response.into_bytes().as_slice()), @@ -956,9 +956,9 @@ mod tests { enum Authenticate { Auth }; impl Authenticator for Authenticate { type Response = Vec; - fn process(&self, challenge: Vec) -> Self::Response { + fn process(&self, challenge: &[u8]) -> Self::Response { assert!( - challenge == b"bar".to_vec(), + challenge == b"bar", "Invalid authenticate challenge" ); b"foo".to_vec() From 033c23ef11d868ec7590b90b6cb79522d2e60f07 Mon Sep 17 00:00:00 2001 From: Kim Minh Kaplan Date: Sat, 10 Nov 2018 09:15:23 +0000 Subject: [PATCH 6/6] Document that Authenticator does the Base64 encoding and decoding. --- src/authenticator.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/authenticator.rs b/src/authenticator.rs index ecfe93d..1351b7a 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -1,5 +1,16 @@ /// This will allow plugable authentication mechanisms. +/// +/// This trait is used by `Client::authenticate` to [authenticate +/// using SASL](https://tools.ietf.org/html/rfc3501#section-6.2.2). pub trait Authenticator { + /// Type of the response to the challenge. This will usually be a + /// `Vec` or `String`. It must not be Base64 encoded: the + /// library will do it. type Response: AsRef<[u8]>; + /// For each server challenge is passed to `process`. The library + /// has already decoded the Base64 string into bytes. + /// + /// The `process` function should return its response, not Base64 + /// encoded: the library will do it. fn process(&self, &[u8]) -> Self::Response; }