use ldap3_proto::LdapMsg; use ldap3_proto::proto::LdapOp; use crate::db::{Database, DatabaseInterface}; use crate::error::GlobalError; use crate::ldap::{ LdapClientState, LdapStream, LdapStreamError, op_bind, op_ext, search_by_mail_filter, }; use crate::listener::Listener; pub async fn ldap_listen(listener: Listener, db: Database) { // If the connection is None, it's because the client aborted early // so there's nothing to do about it. loop { match listener.accept_ldap().await { Ok(Some(stream)) => { let db = db.clone(); tokio::spawn(ldap_handler(stream, db)); } Ok(None) => { panic!("LDAP listener closed"); } Err(e) => { panic!("Failed to listen on LDAP listener"); } } } } #[tracing::instrument(name = "ldap", skip(stream, db), fields(session = %stream.session))] pub async fn ldap_handler(mut stream: LdapStream, mut db: Database) { tracing::info! { remote_addr = ?stream.remote_addr, "New client connection" }; let mut state = LdapClientState::new(); loop { 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, "Closing connection" ); return; } } } } /// 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( stream: &mut LdapStream, msg: LdapMsg, client_state: &mut LdapClientState, db: &mut Database, ) -> Result { tracing::debug!(msg = ?msg, "Received LDAP message"); 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 { client_state.unbind(); tracing::debug!("Failed bind or anonymous bind"); // We keep the connection open in case it's an anonymous bind Ok(true) } } LdapMsg { msgid, op: LdapOp::SearchRequest(sr), // TODO: ctrl for pagination ctrl: _, } => { search_by_mail_filter(stream, db, sr, msgid).await?; Ok(true) } // Unsupported message _ => { tracing::warn!("Unsupported client message, closing connection"); Ok(false) } } }