Merge pull request 'Debug the Dovecot Auth Protocol' (#95) from bug/dovecot-auth-resp into main

Reviewed-on: #95
This commit is contained in:
Quentin 2024-02-13 16:13:53 +00:00
commit d50b1dc178
3 changed files with 74 additions and 68 deletions

View file

@ -54,21 +54,6 @@
]; ];
}; };
pkgVanilla = import nixpkgs { system = "x86_64-linux"; };
shell = pkgVanilla.mkShell {
buildInputs = [
cargo2nix.packages.x86_64-linux.default
fenix.packages.x86_64-linux.minimal.toolchain
fenix.packages.x86_64-linux.rust-analyzer
];
shellHook = ''
echo "AEROGRAME DEVELOPMENT SHELL ${fenix.packages.x86_64-linux.minimal.rustc}"
export RUST_SRC_PATH="${fenix.packages.x86_64-linux.latest.rust-src}/lib/rustlib/src/rust/library"
export RUST_ANALYZER_INTERNALS_DO_NOT_USE='this is unstable'
'';
};
rustTarget = if targetHost == "armv6l-unknown-linux-musleabihf" then "arm-unknown-linux-musleabihf" else targetHost; rustTarget = if targetHost == "armv6l-unknown-linux-musleabihf" then "arm-unknown-linux-musleabihf" else targetHost;
# release builds # release builds
@ -180,9 +165,9 @@
version = crate.version; version = crate.version;
}; };
packages = { packages = {
inherit fhs container;
debug = (rustDebug.workspace.aerogramme {}).bin; debug = (rustDebug.workspace.aerogramme {}).bin;
aerogramme = bin; aerogramme = bin;
container = container;
default = self.packages.${targetHost}.aerogramme; default = self.packages.${targetHost}.aerogramme;
}; };
}); });
@ -197,6 +182,20 @@
}; };
alba = albatros.packages.x86_64-linux.alba; alba = albatros.packages.x86_64-linux.alba;
# Shell
shell = gpkgs.mkShell {
buildInputs = [
cargo2nix.packages.x86_64-linux.default
fenix.packages.x86_64-linux.minimal.toolchain
fenix.packages.x86_64-linux.rust-analyzer
];
shellHook = ''
echo "AEROGRAME DEVELOPMENT SHELL ${fenix.packages.x86_64-linux.minimal.rustc}"
export RUST_SRC_PATH="${fenix.packages.x86_64-linux.latest.rust-src}/lib/rustlib/src/rust/library"
export RUST_ANALYZER_INTERNALS_DO_NOT_USE='this is unstable'
'';
};
# Used only to fetch the "version" # Used only to fetch the "version"
version = platformArtifacts.meta.x86_64-unknown-linux-musl.version; version = platformArtifacts.meta.x86_64-unknown-linux-musl.version;
@ -224,6 +223,7 @@
in in
{ {
devShells.x86_64-linux.default = shell;
packages = { packages = {
x86_64-linux = { x86_64-linux = {
inherit build push; inherit build push;

View file

@ -202,7 +202,41 @@ enum State {
const SERVER_MAJOR: u64 = 1; const SERVER_MAJOR: u64 = 1;
const SERVER_MINOR: u64 = 2; const SERVER_MINOR: u64 = 2;
const EMPTY_AUTHZ: &[u8] = &[];
impl State { 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) { async fn progress(&mut self, cmd: ClientCommand, login: &ArcLoginProvider) {
let new_state = 'state: { let new_state = 'state: {
match (std::mem::replace(self, State::Error), cmd) { match (std::mem::replace(self, State::Error), cmd) {
@ -219,8 +253,18 @@ impl State {
Self::HandshakeDone 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 { if mech != Mechanism::Plain {
tracing::error!(mechanism=?mech, "Unsupported Authentication Mechanism"); tracing::error!(mechanism=?mech, "Unsupported Authentication Mechanism");
break 'state Self::AuthDone { 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 }) => { (Self::AuthPlainProgress { id }, ClientCommand::Cont { id: cid, data }) => {
// Check that ID matches // Check that ID matches
@ -245,53 +295,9 @@ impl State {
}; };
} }
// Check that we can extract user's login+pass Self::AuthDone {
let (ubin, pbin) = match auth_plain(&data) { id,
Ok(([], ([], user, pass))) => (user, pass), res: self.try_auth_plain(&data, login).await,
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),
}
}
} }
} }
_ => { _ => {

View file

@ -16,7 +16,7 @@ use tokio::select;
use tokio::sync::watch; use tokio::sync::watch;
use tokio_util::compat::*; 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 smtp_server::{reply, Config, ConnectionMetadata, Decision, MailMetadata};
use crate::config::*; use crate::config::*;
@ -186,7 +186,7 @@ impl Config for LmtpServer {
// Unescape email, shrink it also to remove last dot // Unescape email, shrink it also to remove last dot
let unesc_res = DataUnescaper::new(true).unescape(&mut text); let unesc_res = DataUnescaper::new(true).unescape(&mut text);
text.truncate(unesc_res.written); 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) { let encrypted_message = match EncryptedMessage::new(text) {
Ok(x) => Arc::new(x), Ok(x) => Arc::new(x),