120 lines
3.5 KiB
Rust
120 lines
3.5 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);
|
||
|
|
|
||
|
|
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)
|
||
|
|
}
|
||
|
|
}
|