Skip to content

Commit 0ab87eb

Browse files
committed
Make rsasl optional
1 parent 6f27543 commit 0ab87eb

File tree

3 files changed

+95
-10
lines changed

3 files changed

+95
-10
lines changed

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ regex = "1.0"
2626
bufstream = "0.1.3"
2727
imap-proto = "0.16.1"
2828
nom = { version = "7.1.0", default-features = false }
29-
rsasl = { version = "2.0.0-rc.2", default-features = false, features = ["provider_base64"] }
29+
rsasl = { version = "2.0.0-rc.2", default-features = false, features = ["provider_base64"], optional = true }
3030
base64 = "0.13.0"
3131
chrono = { version = "0.4", default-features = false, features = ["std"]}
3232
lazy_static = "1.4"
@@ -74,4 +74,4 @@ required-features = ["default"]
7474

7575
[[example]]
7676
name = "rustls_sasl"
77-
required-features = ["rustls-tls"]
77+
required-features = ["rustls-tls", "rsasl"]

src/client.rs

+82-8
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use std::io::{Read, Write};
66
use std::ops::{Deref, DerefMut};
77
use std::str;
88
use std::sync::mpsc;
9-
use std::sync::Arc;
9+
10+
#[cfg(feature = "rsasl")]
1011
use rsasl::prelude::{Mechname, SASLClient, SASLConfig, Session as SASLSession, State as SASLState};
1112

1213
use super::authenticator::Authenticator;
@@ -367,7 +368,7 @@ impl<T: Read + Write> Client<T> {
367368
/// match client.login("user", "pass") {
368369
/// Ok(s) => {
369370
/// // you are successfully authenticated!
370-
/// },
371+
/// }
371372
/// Err((e, orig_client)) => {
372373
/// eprintln!("error logging in: {}", e);
373374
/// // prompt user and try again with orig_client here
@@ -425,7 +426,7 @@ impl<T: Read + Write> Client<T> {
425426
/// match client.authenticate("XOAUTH2", &auth) {
426427
/// Ok(session) => {
427428
/// // you are successfully authenticated!
428-
/// },
429+
/// }
429430
/// Err((e, orig_client)) => {
430431
/// eprintln!("error authenticating: {}", e);
431432
/// // prompt user and try again with orig_client here
@@ -434,9 +435,82 @@ impl<T: Read + Write> Client<T> {
434435
/// };
435436
/// }
436437
/// ```
437-
pub fn authenticate(
438+
pub fn authenticate<A: Authenticator>(
438439
mut self,
439-
config: Arc<SASLConfig>,
440+
auth_type: impl AsRef<str>,
441+
authenticator: &A,
442+
) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
443+
ok_or_unauth_client_err!(
444+
self.run_command(&format!("AUTHENTICATE {}", auth_type.as_ref())),
445+
self
446+
);
447+
self.do_auth_handshake(authenticator)
448+
}
449+
450+
/// This func does the handshake process once the authenticate command is made.
451+
fn do_auth_handshake<A: Authenticator>(
452+
mut self,
453+
authenticator: &A,
454+
) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
455+
// TODO Clean up this code
456+
loop {
457+
let mut line = Vec::new();
458+
459+
// explicit match blocks neccessary to convert error to tuple and not bind self too
460+
// early (see also comment on `login`)
461+
ok_or_unauth_client_err!(self.readline(&mut line), self);
462+
463+
// ignore server comments
464+
if line.starts_with(b"* ") {
465+
continue;
466+
}
467+
468+
// Some servers will only send `+\r\n`.
469+
if line.starts_with(b"+ ") || &line == b"+\r\n" {
470+
let challenge = if &line == b"+\r\n" {
471+
Vec::new()
472+
} else {
473+
let line_str = ok_or_unauth_client_err!(
474+
match str::from_utf8(line.as_slice()) {
475+
Ok(line_str) => Ok(line_str),
476+
Err(e) => Err(Error::Parse(ParseError::DataNotUtf8(line, e))),
477+
},
478+
self
479+
);
480+
let data =
481+
ok_or_unauth_client_err!(parse_authenticate_response(line_str), self);
482+
ok_or_unauth_client_err!(
483+
base64::decode(data).map_err(|e| Error::Parse(ParseError::Authentication(
484+
data.to_string(),
485+
Some(e)
486+
))),
487+
self
488+
)
489+
};
490+
491+
let raw_response = &authenticator.process(&challenge);
492+
let auth_response = base64::encode(raw_response);
493+
ok_or_unauth_client_err!(
494+
self.write_line(auth_response.into_bytes().as_slice()),
495+
self
496+
);
497+
} else {
498+
ok_or_unauth_client_err!(self.read_response_onto(&mut line), self);
499+
return Ok(Session::new(self.conn));
500+
}
501+
}
502+
}
503+
}
504+
505+
#[cfg(feature = "rsasl")]
506+
impl<T: Read + Write> Client<T> {
507+
508+
/// Authenticate with the server using the given custom SASLConfig to handle the server's
509+
/// challenge.
510+
///
511+
pub fn sasl_auth(
512+
mut self,
513+
config: ::std::sync::Arc<SASLConfig>,
440514
mechanism: &Mechname,
441515
) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
442516
let client = SASLClient::new(config);
@@ -450,11 +524,11 @@ impl<T: Read + Write> Client<T> {
450524
self.run_command(&format!("AUTHENTICATE {}", mechanism.as_str())),
451525
self
452526
);
453-
self.do_auth_handshake(session)
527+
self.do_sasl_handshake(session)
454528
}
455529

456-
/// This func does the handshake process once the authenticate command is made.
457-
fn do_auth_handshake(
530+
/// This func does the SASL handshake process once the authenticate command is made.
531+
fn do_sasl_handshake(
458532
mut self,
459533
mut authenticator: SASLSession,
460534
) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {

src/error.rs

+11
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use imap_proto::{types::ResponseCode, Response};
1414
use native_tls::Error as TlsError;
1515
#[cfg(feature = "native-tls")]
1616
use native_tls::HandshakeError as TlsHandshakeError;
17+
#[cfg(feature = "rsasl")]
1718
use rsasl::prelude::{SASLError, SessionError as SASLSessionError};
1819
#[cfg(feature = "rustls-tls")]
1920
use rustls_connector::HandshakeError as RustlsHandshakeError;
@@ -93,9 +94,11 @@ pub enum Error {
9394
ConnectionLost,
9495
/// Error parsing a server response.
9596
Parse(ParseError),
97+
#[cfg(feature = "rsasl")]
9698
/// Error occurred when tyring to set up authentication
9799
AuthenticationSetup(SASLError),
98100
/// Error occurred during authentication
101+
#[cfg(feature = "rsasl")]
99102
Authentication(SASLSessionError),
100103
/// Command inputs were not valid [IMAP
101104
/// strings](https://tools.ietf.org/html/rfc3501#section-4.3).
@@ -123,11 +126,13 @@ impl From<ParseError> for Error {
123126
}
124127
}
125128

129+
#[cfg(feature = "rsasl")]
126130
impl From<SASLError> for Error {
127131
fn from(err: SASLError) -> Self {
128132
Error::AuthenticationSetup(err)
129133
}
130134
}
135+
#[cfg(feature = "rsasl")]
131136
impl From<SASLSessionError> for Error {
132137
fn from(err: SASLSessionError) -> Self {
133138
Error::Authentication(err)
@@ -186,7 +191,9 @@ impl fmt::Display for Error {
186191
Error::Append => f.write_str("Could not append mail to mailbox"),
187192
Error::Unexpected(ref r) => write!(f, "Unexpected Response: {:?}", r),
188193
Error::MissingStatusResponse => write!(f, "Missing STATUS Response"),
194+
#[cfg(feature = "rsasl")]
189195
Error::AuthenticationSetup(ref e) => fmt::Display::fmt(e, f),
196+
#[cfg(feature = "rsasl")]
190197
Error::Authentication(ref e) => fmt::Display::fmt(e, f),
191198
}
192199
}
@@ -212,7 +219,9 @@ impl StdError for Error {
212219
Error::Append => "Could not append mail to mailbox",
213220
Error::Unexpected(_) => "Unexpected Response",
214221
Error::MissingStatusResponse => "Missing STATUS Response",
222+
#[cfg(feature = "rsasl")]
215223
Error::AuthenticationSetup(_) => "Failed to setup authentication",
224+
#[cfg(feature = "rsasl")]
216225
Error::Authentication(_) => "Authentication Failed",
217226
}
218227
}
@@ -227,7 +236,9 @@ impl StdError for Error {
227236
#[cfg(feature = "native-tls")]
228237
Error::TlsHandshake(ref e) => Some(e),
229238
Error::Parse(ParseError::DataNotUtf8(_, ref e)) => Some(e),
239+
#[cfg(feature = "rsasl")]
230240
Error::AuthenticationSetup(ref e) => Some(e),
241+
#[cfg(feature = "rsasl")]
231242
Error::Authentication(ref e) => Some(e),
232243
_ => None,
233244
}

0 commit comments

Comments
 (0)