feat: Implement LDAP bind/whoami

This commit is contained in:
selfhoster selfhoster 2026-09-01 21:18:19 +02:00
commit b4330b37fa
19 changed files with 664 additions and 37 deletions

69
vendor/dn_escape/src/lib.rs vendored Normal file
View file

@ -0,0 +1,69 @@
// CODE DEVELOPED BY THE LDAP3 PROJECT, UNDER MIT LICENSE.
use std::borrow::Cow;
/// Escape an attribute value in a relative distinguished name (RDN).
///
/// When a literal string is used to represent an attribute value in an RDN,
/// some of its characters might need to be escaped according to the rules
/// of [RFC 4514](https://tools.ietf.org/html/rfc4514).
///
/// The function is named `dn_escape()` instead of `rdn_escape()` because of
/// a long-standing association of its intended use with the handling of DNs.
///
/// The argument, `val`, can be owned or borrowed. The function doesn't
/// allocate the return value unless there's need to escape the input.
pub fn dn_escape<'a, S: Into<Cow<'a, str>>>(val: S) -> Cow<'a, str> {
#[inline]
fn always_escape(c: u8) -> bool {
c == b'"'
|| c == b'+'
|| c == b','
|| c == b';'
|| c == b'<'
|| c == b'='
|| c == b'>'
|| c == b'\\'
|| c == 0
}
#[inline]
fn escape_leading(c: u8) -> bool {
c == b' ' || c == b'#'
}
#[inline]
fn escape_trailing(c: u8) -> bool {
c == b' '
}
#[inline]
fn xdigit(c: u8) -> u8 {
c + if c < 10 { b'0' } else { b'a' - 10 }
}
let val = val.into();
let mut output = None;
for (i, &c) in val.as_bytes().iter().enumerate() {
if always_escape(c)
|| i == 0 && escape_leading(c)
|| i + 1 == val.len() && escape_trailing(c)
{
if output.is_none() {
output = Some(Vec::with_capacity(val.len() + 12)); // guess: up to 4 escaped chars
output.as_mut().unwrap().extend(val[..i].as_bytes());
}
let output = output.as_mut().unwrap();
output.push(b'\\');
output.push(xdigit(c >> 4));
output.push(xdigit(c & 0xF));
} else if let Some(ref mut output) = output {
output.push(c);
}
}
if let Some(output) = output {
Cow::Owned(String::from_utf8(output).expect("dn escaped"))
} else {
val
}
}