Skip to content

Commit 9614ef1

Browse files
committed
Make rsasl optional
1 parent b627592 commit 9614ef1

File tree

3 files changed

+95
-10
lines changed

3 files changed

+95
-10
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
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

Lines changed: 82 additions & 8 deletions
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;
@@ -361,7 +362,7 @@ impl<T: Read + Write> Client<T> {
361362
/// match client.login("user", "pass") {
362363
/// Ok(s) => {
363364
/// // you are successfully authenticated!
364-
/// },
365+
/// }
365366
/// Err((e, orig_client)) => {
366367
/// eprintln!("error logging in: {}", e);
367368
/// // prompt user and try again with orig_client here
@@ -419,7 +420,7 @@ impl<T: Read + Write> Client<T> {
419420
/// match client.authenticate("XOAUTH2", &auth) {
420421
/// Ok(session) => {
421422
/// // you are successfully authenticated!
422-
/// },
423+
/// }
423424
/// Err((e, orig_client)) => {
424425
/// eprintln!("error authenticating: {}", e);
425426
/// // prompt user and try again with orig_client here
@@ -428,9 +429,82 @@ impl<T: Read + Write> Client<T> {
428429
/// };
429430
/// }
430431
/// ```
431-
pub fn authenticate(
432+
pub fn authenticate<A: Authenticator>(
432433
mut self,
433-
config: Arc<SASLConfig>,
434+
auth_type: impl AsRef<str>,
435+
authenticator: &A,
436+
) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
437+
ok_or_unauth_client_err!(
438+
self.run_command(&format!("AUTHENTICATE {}", auth_type.as_ref())),
439+
self
440+
);
441+
self.do_auth_handshake(authenticator)
442+
}
443+
444+
/// This func does the handshake process once the authenticate command is made.
445+
fn do_auth_handshake<A: Authenticator>(
446+
mut self,
447+
authenticator: &A,
448+
) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
449+
// TODO Clean up this code
450+
loop {
451+
let mut line = Vec::new();
452+
453+
// explicit match blocks neccessary to convert error to tuple and not bind self too
454+
// early (see also comment on `login`)
455+
ok_or_unauth_client_err!(self.readline(&mut line), self);
456+
457+
// ignore server comments
458+
if line.starts_with(b"* ") {
459+
continue;
460+
}
461+
462+
// Some servers will only send `+\r\n`.
463+
if line.starts_with(b"+ ") || &line == b"+\r\n" {
464+
let challenge = if &line == b"+\r\n" {
465+
Vec::new()
466+
} else {
467+
let line_str = ok_or_unauth_client_err!(
468+
match str::from_utf8(line.as_slice()) {
469+
Ok(line_str) => Ok(line_str),
470+
Err(e) => Err(Error::Parse(ParseError::DataNotUtf8(line, e))),
471+
},
472+
self
473+
);
474+
let data =
475+
ok_or_unauth_client_err!(parse_authenticate_response(line_str), self);
476+
ok_or_unauth_client_err!(
477+
base64::decode(data).map_err(|e| Error::Parse(ParseError::Authentication(
478+
data.to_string(),
479+
Some(e)
480+
))),
481+
self
482+
)
483+
};
484+
485+
let raw_response = &authenticator.process(&challenge);
486+
let auth_response = base64::encode(raw_response);
487+
ok_or_unauth_client_err!(
488+
self.write_line(auth_response.into_bytes().as_slice()),
489+
self
490+
);
491+
} else {
492+
ok_or_unauth_client_err!(self.read_response_onto(&mut line), self);
493+
return Ok(Session::new(self.conn));
494+
}
495+
}
496+
}
497+
}
498+
499+
#[cfg(feature = "rsasl")]
500+
impl<T: Read + Write> Client<T> {
501+
502+
/// Authenticate with the server using the given custom SASLConfig to handle the server's
503+
/// challenge.
504+
///
505+
pub fn sasl_auth(
506+
mut self,
507+
config: ::std::sync::Arc<SASLConfig>,
434508
mechanism: &Mechname,
435509
) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {
436510
let client = SASLClient::new(config);
@@ -444,11 +518,11 @@ impl<T: Read + Write> Client<T> {
444518
self.run_command(&format!("AUTHENTICATE {}", mechanism.as_str())),
445519
self
446520
);
447-
self.do_auth_handshake(session)
521+
self.do_sasl_handshake(session)
448522
}
449523

450-
/// This func does the handshake process once the authenticate command is made.
451-
fn do_auth_handshake(
524+
/// This func does the SASL handshake process once the authenticate command is made.
525+
fn do_sasl_handshake(
452526
mut self,
453527
mut authenticator: SASLSession,
454528
) -> ::std::result::Result<Session<T>, (Error, Client<T>)> {

src/error.rs

Lines changed: 11 additions & 0 deletions
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)