148 lines
4.6 KiB
Rust
148 lines
4.6 KiB
Rust
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);
|
|
|
|
if lbr.dn == "" {
|
|
// Here we pretend to have successfully bound so that
|
|
// a client performing an anonymous bind can proceed with
|
|
// more requests (such as a search request).
|
|
// This supports ldap search which always performs a bind.
|
|
let resp_msg = LdapMsg {
|
|
msgid,
|
|
op: LdapOp::BindResponse(LdapBindResponse {
|
|
res: LdapResult {
|
|
code: LdapResultCode::Success,
|
|
matcheddn: "".to_string(),
|
|
message: "".to_string(),
|
|
referral: vec![],
|
|
},
|
|
saslcreds: None,
|
|
}),
|
|
ctrl: vec![],
|
|
};
|
|
w.send(resp_msg).await.map_err(|err| {
|
|
error!("Unable to send response: {err}");
|
|
LdapError::Transport
|
|
})?;
|
|
// We still treat the client as unbounded because it doesn't
|
|
// have a session to a backend.
|
|
return Ok(Some(ClientState::Unbound));
|
|
}
|
|
|
|
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 {
|
|
// TODO: we should probably return an error to the client here
|
|
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 backend_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 `{request_dn}` -> `{backend_dn}`");
|
|
Ok(Some(ClientState::Authenticated {
|
|
request_dn,
|
|
backend_dn,
|
|
client,
|
|
}))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|