feat: Implement LDAP bind/whoami

This commit is contained in:
selfhoster selfhoster 2026-09-01 21:18:19 +02:00
commit b4330b37fa
19 changed files with 664 additions and 37 deletions

View file

@ -1,26 +1,35 @@
use ldap3_proto::LdapMsg;
use ldap3_proto::proto::LdapOp;
use crate::db::{Database, DatabaseInterface};
use crate::ldap::{LdapStream, LdapStreamError};
use crate::ldap::{LdapClientState, LdapStream, LdapStreamError, op_bind, op_ext};
#[tracing::instrument(name = "ldap", skip(s, db), fields(session = %s.session))]
pub async fn ldap_handler<D: DatabaseInterface>(mut s: LdapStream, db: Database<D>) {
#[tracing::instrument(name = "ldap", skip(stream, db), fields(session = %stream.session))]
pub async fn ldap_handler<D: DatabaseInterface>(mut stream: LdapStream, mut db: Database<D>) {
tracing::info! {
remote_addr = ?s.remote_addr,
remote_addr = ?stream.remote_addr,
"New client connection"
};
let mut state = LdapClientState::new();
loop {
match s.next().await {
Ok(msg) => {
if let Err(e) = ldap_handler_inner(msg).await {
match stream.next().await {
Ok(msg) => match ldap_handler_inner(&mut stream, msg, &mut state, &mut db).await {
Ok(should_keep_alive) => {
if !should_keep_alive {
tracing::debug!("Finished connection");
return;
}
}
Err(e) => {
tracing::debug!(
reason = ?e,
"Failed to respond"
);
return;
}
}
},
Err(e) => {
tracing::debug!(
reason = ?e,
@ -32,7 +41,53 @@ pub async fn ldap_handler<D: DatabaseInterface>(mut s: LdapStream, db: Database<
}
}
pub async fn ldap_handler_inner(msg: LdapMsg) -> Result<(), LdapStreamError> {
/// Return true to keep the connection going, false to close it.
#[tracing::instrument(name = "ldap-handler", skip(client_state, db, stream))]
pub async fn ldap_handler_inner<D: DatabaseInterface>(
stream: &mut LdapStream,
msg: LdapMsg,
client_state: &mut LdapClientState,
db: &mut Database<D>,
) -> Result<bool, LdapStreamError> {
tracing::debug!(msg = ?msg, "Received LDAP message");
Ok(())
match msg {
// Disconnect
LdapMsg {
msgid: _,
op: LdapOp::UnbindRequest,
ctrl: _,
} => {
client_state.unbind();
// TODO: keep the connection open?
Ok(true)
}
LdapMsg {
msgid,
op: LdapOp::ExtendedRequest(ler),
ctrl: _,
} => {
op_ext(stream, ler, msgid, client_state).await?;
Ok(true)
}
LdapMsg {
msgid,
op: LdapOp::BindRequest(lbr),
ctrl: _,
} => {
if let Some(bound_dn) = op_bind(stream, db, lbr, msgid).await? {
tracing::debug!("Successful bind");
client_state.bind(bound_dn);
Ok(true)
} else {
tracing::debug!("Unsuccessful bind");
// TODO: abort connection here?
Ok(false)
}
}
// Unsupported message
_ => {
tracing::warn!("Unsupported client message, closing connection");
Ok(false)
}
}
}