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: &mut FramedWrite, mut lbr: LdapBindRequest, config: Arc, msgid: i32, ctrl: Vec, ) -> Result, 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) } }