feat: Initial implementation (bind/search/whoami)
This commit is contained in:
commit
d42508b15e
14 changed files with 2325 additions and 0 deletions
120
src/op/bind.rs
Normal file
120
src/op/bind.rs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
use futures_util::SinkExt;
|
||||
use ldap3_proto::LdapCodec;
|
||||
use ldap3_proto::control::*;
|
||||
use ldap3_proto::proto::*;
|
||||
use tokio::io::AsyncWrite;
|
||||
use tokio_util::codec::FramedWrite;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{BasicLdapClient, ClientState, Config, Dn, LdapError};
|
||||
|
||||
pub fn bind_operror(msgid: i32, msg: &str) -> LdapMsg {
|
||||
LdapMsg {
|
||||
msgid,
|
||||
op: LdapOp::BindResponse(LdapBindResponse {
|
||||
res: LdapResult {
|
||||
code: LdapResultCode::OperationsError,
|
||||
matcheddn: "".to_string(),
|
||||
message: msg.to_string(),
|
||||
referral: vec![],
|
||||
},
|
||||
saslcreds: None,
|
||||
}),
|
||||
ctrl: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn bind<W: AsyncWrite + Unpin>(
|
||||
w: &mut FramedWrite<W, LdapCodec>,
|
||||
mut lbr: LdapBindRequest,
|
||||
config: Arc<Config>,
|
||||
msgid: i32,
|
||||
ctrl: Vec<LdapControl>,
|
||||
) -> Result<Option<ClientState>, LdapError> {
|
||||
trace!("{:?}", lbr);
|
||||
|
||||
let request_dn = lbr.dn.clone();
|
||||
|
||||
debug!("Received bind request on DN: {}", lbr.dn);
|
||||
let mut dn = Dn::from_dn_str(&lbr.dn)?;
|
||||
|
||||
let Some(requested_domain) = dn.get_hostname() else {
|
||||
debug!("No domain name CN found in DN: {}", lbr.dn);
|
||||
return Err(LdapError::InvalidQuery);
|
||||
};
|
||||
|
||||
// Lowercase the domain systematically to allow matches
|
||||
let requested_domain = requested_domain.to_lowercase();
|
||||
|
||||
let Some(mapping) = config
|
||||
.mapping
|
||||
.iter()
|
||||
.find(|x| x.from.to_lowercase() == requested_domain)
|
||||
else {
|
||||
debug!("No mapping found for domain {requested_domain}");
|
||||
return Err(LdapError::InvalidQuery);
|
||||
};
|
||||
|
||||
debug!(
|
||||
"Redirecting {} to {} with domain {}",
|
||||
requested_domain, mapping.backend, mapping.to
|
||||
);
|
||||
dn.set_hostname(&mapping.to, false);
|
||||
lbr.dn = dn.to_dn_string();
|
||||
let dn = lbr.dn.clone();
|
||||
|
||||
// We need the client to connect *and* bind to proceed here!
|
||||
let mut client = match BasicLdapClient::build(&mapping.backend).await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
error!("A client build error has occurred: {e:?}");
|
||||
let resp_msg = bind_operror(msgid, "unable to bind");
|
||||
w.send(resp_msg).await.map_err(|err| {
|
||||
error!("Unable to send response: {err}");
|
||||
LdapError::Transport
|
||||
})?;
|
||||
// Always bail.
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
let valid = match client.bind(lbr, ctrl).await {
|
||||
Ok((bind_resp, ctrl)) => {
|
||||
// Almost there, lets check the bind result.
|
||||
let valid = bind_resp.res.code == LdapResultCode::Success;
|
||||
|
||||
let resp_msg = LdapMsg {
|
||||
msgid,
|
||||
op: LdapOp::BindResponse(bind_resp),
|
||||
ctrl,
|
||||
};
|
||||
w.send(resp_msg).await.map_err(|err| {
|
||||
error!("Unable to send response: {err}");
|
||||
LdapError::Transport
|
||||
})?;
|
||||
valid
|
||||
}
|
||||
Err(e) => {
|
||||
error!("A client bind error has occurred: {e:?}");
|
||||
let resp_msg = bind_operror(msgid, "unable to bind");
|
||||
w.send(resp_msg).await.map_err(|err| {
|
||||
error!("Unable to send response: {err}");
|
||||
LdapError::Transport
|
||||
})?;
|
||||
// Always bail.
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
|
||||
if valid {
|
||||
info!("Successful bind for {}", dn);
|
||||
Ok(Some(ClientState::Authenticated {
|
||||
request_dn,
|
||||
backend_dn: dn,
|
||||
client,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue