From 8a803bfb11faa11e04b8eb36cbc39acbcde948f0 Mon Sep 17 00:00:00 2001 From: Valentin Tolmer Date: Wed, 17 Sep 2025 00:47:53 +0200 Subject: [PATCH] ldap: normalize base DN in LdapInfo, reduce memory usage By making it a &'static, we can have a single allocation for all the threads/async contexts. This also normalizes the whitespace from the user input; a trailing \n can cause weird issues with clients --- crates/ldap/src/core/utils.rs | 27 ++++++++++++++++++ crates/ldap/src/handler.rs | 38 ++++++++++--------------- crates/ldap/src/lib.rs | 2 +- crates/ldap/src/search.rs | 3 +- server/src/ldap_server.rs | 52 ++++++++++++++--------------------- 5 files changed, 63 insertions(+), 59 deletions(-) diff --git a/crates/ldap/src/core/utils.rs b/crates/ldap/src/core/utils.rs index da80027..81a5d4e 100644 --- a/crates/ldap/src/core/utils.rs +++ b/crates/ldap/src/core/utils.rs @@ -300,6 +300,23 @@ pub struct LdapInfo { pub ignored_group_attributes: Vec, } +impl LdapInfo { + pub fn new( + base_dn: &str, + ignored_user_attributes: Vec, + ignored_group_attributes: Vec, + ) -> LdapResult { + let base_dn = parse_distinguished_name(&base_dn.to_ascii_lowercase())?; + let base_dn_str = join(base_dn.iter().map(|(k, v)| format!("{k}={v}")), ","); + Ok(Self { + base_dn, + base_dn_str, + ignored_user_attributes, + ignored_group_attributes, + }) + } +} + pub fn get_custom_attribute( attributes: &[Attribute], attribute_name: &AttributeName, @@ -521,4 +538,14 @@ mod tests { parsed_dn ); } + + #[test] + fn test_whitespace_in_ldap_info() { + assert_eq!( + LdapInfo::new(" ou=people, dc =example, dc=com \n", vec![], vec![]) + .unwrap() + .base_dn_str, + "ou=people,dc=example,dc=com" + ); + } } diff --git a/crates/ldap/src/handler.rs b/crates/ldap/src/handler.rs index 7197a3d..863f753 100644 --- a/crates/ldap/src/handler.rs +++ b/crates/ldap/src/handler.rs @@ -2,7 +2,7 @@ use crate::{ compare, core::{ error::{LdapError, LdapResult}, - utils::{LdapInfo, parse_distinguished_name}, + utils::LdapInfo, }, create, delete, modify, password::{self, do_password_modification}, @@ -18,7 +18,7 @@ use ldap3_proto::proto::{ }; use lldap_access_control::AccessControlledBackendHandler; use lldap_auth::access_control::ValidationResults; -use lldap_domain::{public_schema::PublicSchema, types::AttributeName}; +use lldap_domain::public_schema::PublicSchema; use lldap_domain_handlers::handler::{BackendHandler, LoginHandler, ReadSchemaBackendHandler}; use lldap_opaque_handler::OpaqueHandler; use tracing::{debug, instrument}; @@ -59,7 +59,7 @@ pub(crate) fn make_modify_response(code: LdapResultCode, message: String) -> Lda pub struct LdapHandler { user_info: Option, backend_handler: AccessControlledBackendHandler, - ldap_info: LdapInfo, + ldap_info: &'static LdapInfo, session_uuid: uuid::Uuid, } @@ -89,23 +89,13 @@ enum Credentials<'s> { impl LdapHandler { pub fn new( backend_handler: AccessControlledBackendHandler, - mut ldap_base_dn: String, - ignored_user_attributes: Vec, - ignored_group_attributes: Vec, + ldap_info: &'static LdapInfo, session_uuid: uuid::Uuid, ) -> Self { - ldap_base_dn.make_ascii_lowercase(); Self { user_info: None, backend_handler, - ldap_info: LdapInfo { - base_dn: parse_distinguished_name(&ldap_base_dn).unwrap_or_else(|_| { - panic!("Invalid value for ldap_base_dn in configuration: {ldap_base_dn}") - }), - base_dn_str: ldap_base_dn, - ignored_user_attributes, - ignored_group_attributes, - }, + ldap_info, session_uuid, } } @@ -114,9 +104,9 @@ impl LdapHandler Self { Self::new( AccessControlledBackendHandler::new(backend_handler), - ldap_base_dn.to_string(), - vec![], - vec![], + Box::leak(Box::new( + LdapInfo::new(ldap_base_dn, Vec::new(), Vec::new()).unwrap(), + )), uuid::Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap(), ) } @@ -171,13 +161,13 @@ impl LdapHandler Vec { let (code, message) = - match password::do_bind(&self.ldap_info, request, self.get_login_handler()).await { + match password::do_bind(self.ldap_info, request, self.get_login_handler()).await { Ok(user_id) => { self.user_info = self .backend_handler @@ -211,7 +201,7 @@ impl LdapHandler LdapHandler LdapHandler LdapHandler( stream: Stream, backend_handler: Backend, - ldap_base_dn: String, - ignored_user_attributes: Vec, - ignored_group_attributes: Vec, + ldap_info: &'static LdapInfo, ) -> Result where Backend: BackendHandler + LoginHandler + OpaqueHandler + 'static, @@ -88,9 +85,7 @@ where let session_uuid = Uuid::new_v4(); let mut session = LdapHandler::new( AccessControlledBackendHandler::new(backend_handler), - ldap_base_dn, - ignored_user_attributes, - ignored_group_attributes, + ldap_info, session_uuid, ); @@ -170,9 +165,19 @@ where { let context = ( backend_handler, - config.ldap_base_dn.clone(), - config.ignored_user_attributes.clone(), - config.ignored_group_attributes.clone(), + Box::leak(Box::new( + LdapInfo::new( + &config.ldap_base_dn, + config.ignored_user_attributes.clone(), + config.ignored_group_attributes.clone(), + ) + .with_context(|| { + format!( + "Invalid value for ldap_base_dn in configuration: {}", + &config.ldap_base_dn + ) + })?, + )) as &'static LdapInfo, ); let context_for_tls = context.clone(); @@ -182,15 +187,8 @@ where fn_service(move |stream: TcpStream| { let context = context.clone(); async move { - let (handler, base_dn, ignored_user_attributes, ignored_group_attributes) = context; - handle_ldap_stream( - stream, - handler, - base_dn, - ignored_user_attributes, - ignored_group_attributes, - ) - .await + let (handler, ldap_info) = context; + handle_ldap_stream(stream, handler, ldap_info).await } }) .map_err(|err: anyhow::Error| error!("[LDAP] Service Error: {:#}", err)) @@ -211,19 +209,9 @@ where fn_service(move |stream: TcpStream| { let tls_context = tls_context.clone(); async move { - let ( - (handler, base_dn, ignored_user_attributes, ignored_group_attributes), - tls_acceptor, - ) = tls_context; + let ((handler, ldap_info), tls_acceptor) = tls_context; let tls_stream = tls_acceptor.accept(stream).await?; - handle_ldap_stream( - tls_stream, - handler, - base_dn, - ignored_user_attributes, - ignored_group_attributes, - ) - .await + handle_ldap_stream(tls_stream, handler, ldap_info).await } }) .map_err(|err: anyhow::Error| error!("[LDAPS] Service Error: {:#}", err))