diff options
author | Quentin <quentin@dufour.io> | 2024-02-13 16:13:53 +0000 |
---|---|---|
committer | Quentin <quentin@dufour.io> | 2024-02-13 16:13:53 +0000 |
commit | d50b1dc178702f112b34e277465e61b2c3cd0a41 (patch) | |
tree | 1981b69a2d46d03b62be72e23fc3698ad1df3e5f /src | |
parent | ede836fc804acef758e50cf629910595e93b11e2 (diff) | |
parent | 9377ca3ef4b274df9b25cc56f38d69e70a61985c (diff) | |
download | aerogramme-d50b1dc178702f112b34e277465e61b2c3cd0a41.tar.gz aerogramme-d50b1dc178702f112b34e277465e61b2c3cd0a41.zip |
Merge pull request 'Debug the Dovecot Auth Protocol' (#95) from bug/dovecot-auth-resp into main
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/aerogramme/pulls/95
Diffstat (limited to 'src')
-rw-r--r-- | src/auth.rs | 106 | ||||
-rw-r--r-- | src/lmtp.rs | 4 |
2 files changed, 58 insertions, 52 deletions
diff --git a/src/auth.rs b/src/auth.rs index 81ba496..064c90c 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -202,7 +202,41 @@ enum State { const SERVER_MAJOR: u64 = 1; const SERVER_MINOR: u64 = 2; +const EMPTY_AUTHZ: &[u8] = &[]; impl State { + async fn try_auth_plain<'a>(&self, data: &'a [u8], login: &ArcLoginProvider) -> AuthRes { + // Check that we can extract user's login+pass + let (ubin, pbin) = match auth_plain(&data) { + Ok(([], (authz, user, pass))) if authz == user || authz == EMPTY_AUTHZ => (user, pass), + Ok(_) => { + tracing::error!("Impersonating user is not supported"); + return AuthRes::Failed(None, None); + } + Err(e) => { + tracing::error!(err=?e, "Could not parse the SASL PLAIN data chunk"); + return AuthRes::Failed(None, None); + } + }; + + // Try to convert it to UTF-8 + let (user, password) = match (std::str::from_utf8(ubin), std::str::from_utf8(pbin)) { + (Ok(u), Ok(p)) => (u, p), + _ => { + tracing::error!("Username or password contain invalid UTF-8 characters"); + return AuthRes::Failed(None, None); + } + }; + + // Try to connect user + match login.login(user, password).await { + Ok(_) => AuthRes::Success(user.to_string()), + Err(e) => { + tracing::warn!(err=?e, "login failed"); + AuthRes::Failed(Some(user.to_string()), None) + } + } + } + async fn progress(&mut self, cmd: ClientCommand, login: &ArcLoginProvider) { let new_state = 'state: { match (std::mem::replace(self, State::Error), cmd) { @@ -219,8 +253,18 @@ impl State { Self::HandshakeDone } - (Self::HandshakeDone { .. }, ClientCommand::Auth { id, mech, .. }) - | (Self::AuthDone { .. }, ClientCommand::Auth { id, mech, .. }) => { + ( + Self::HandshakeDone { .. }, + ClientCommand::Auth { + id, mech, options, .. + }, + ) + | ( + Self::AuthDone { .. }, + ClientCommand::Auth { + id, mech, options, .. + }, + ) => { if mech != Mechanism::Plain { tracing::error!(mechanism=?mech, "Unsupported Authentication Mechanism"); break 'state Self::AuthDone { @@ -229,7 +273,13 @@ impl State { }; } - Self::AuthPlainProgress { id } + match options.last() { + Some(AuthOption::Resp(data)) => Self::AuthDone { + id, + res: self.try_auth_plain(&data, login).await, + }, + _ => Self::AuthPlainProgress { id }, + } } (Self::AuthPlainProgress { id }, ClientCommand::Cont { id: cid, data }) => { // Check that ID matches @@ -245,53 +295,9 @@ impl State { }; } - // Check that we can extract user's login+pass - let (ubin, pbin) = match auth_plain(&data) { - Ok(([], ([], user, pass))) => (user, pass), - Ok(_) => { - tracing::error!("Impersonating user is not supported"); - break 'state Self::AuthDone { - id, - res: AuthRes::Failed(None, None), - }; - } - Err(e) => { - tracing::error!(err=?e, "Could not parse the SASL PLAIN data chunk"); - break 'state Self::AuthDone { - id, - res: AuthRes::Failed(None, None), - }; - } - }; - - // Try to convert it to UTF-8 - let (user, password) = - match (std::str::from_utf8(ubin), std::str::from_utf8(pbin)) { - (Ok(u), Ok(p)) => (u, p), - _ => { - tracing::error!( - "Username or password contain invalid UTF-8 characters" - ); - break 'state Self::AuthDone { - id, - res: AuthRes::Failed(None, None), - }; - } - }; - - // Try to connect user - match login.login(user, password).await { - Ok(_) => Self::AuthDone { - id, - res: AuthRes::Success(user.to_string()), - }, - Err(e) => { - tracing::warn!(err=?e, "login failed"); - Self::AuthDone { - id, - res: AuthRes::Failed(Some(user.to_string()), None), - } - } + Self::AuthDone { + id, + res: self.try_auth_plain(&data, login).await, } } _ => { diff --git a/src/lmtp.rs b/src/lmtp.rs index 28c4d51..dcd4bcc 100644 --- a/src/lmtp.rs +++ b/src/lmtp.rs @@ -16,7 +16,7 @@ use tokio::select; use tokio::sync::watch; use tokio_util::compat::*; -use smtp_message::{Email, EscapedDataReader, DataUnescaper, Reply, ReplyCode}; +use smtp_message::{DataUnescaper, Email, EscapedDataReader, Reply, ReplyCode}; use smtp_server::{reply, Config, ConnectionMetadata, Decision, MailMetadata}; use crate::config::*; @@ -186,7 +186,7 @@ impl Config for LmtpServer { // Unescape email, shrink it also to remove last dot let unesc_res = DataUnescaper::new(true).unescape(&mut text); text.truncate(unesc_res.written); - tracing::debug!(prev_sz=raw_size, new_sz=text.len(), "unescaped"); + tracing::debug!(prev_sz = raw_size, new_sz = text.len(), "unescaped"); let encrypted_message = match EncryptedMessage::new(text) { Ok(x) => Arc::new(x), |