Debug the Dovecot Auth Protocol #95
3 changed files with 74 additions and 68 deletions
32
flake.nix
32
flake.nix
|
@ -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;
|
||||||
|
|
106
src/auth.rs
106
src/auth.rs
|
@ -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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
|
@ -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),
|
||||||
|
|
Loading…
Reference in a new issue