#![allow(clippy::doc_markdown)]
use super::ber::{
BerError, Children, encode_enumerated, encode_octet_string, encode_sequence, parse_bool,
parse_integer, parse_tlv, tlv,
};
use super::filter::{Filter, decode_filter};
const TAG_BIND_REQUEST: u8 = 0x60;
const TAG_BIND_RESPONSE: u8 = 0x61;
const TAG_UNBIND_REQUEST: u8 = 0x42;
const TAG_SEARCH_REQUEST: u8 = 0x63;
const TAG_SEARCH_RESULT_ENTRY: u8 = 0x64;
const TAG_SEARCH_RESULT_DONE: u8 = 0x65;
const TAG_SIMPLE_AUTH: u8 = 0x80;
pub mod result_code {
pub const SUCCESS: i64 = 0;
pub const PROTOCOL_ERROR: i64 = 2;
pub const SIZE_LIMIT_EXCEEDED: i64 = 4;
pub const NO_SUCH_OBJECT: i64 = 32;
pub const INVALID_CREDENTIALS: i64 = 49;
pub const INSUFFICIENT_ACCESS: i64 = 50;
pub const UNWILLING_TO_PERFORM: i64 = 53;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Scope {
Base,
OneLevel,
Subtree,
}
#[derive(Debug, Clone)]
pub enum LdapOp {
Bind(BindRequest),
Search(SearchRequest),
Unbind,
Unsupported(u8),
}
#[derive(Debug, Clone)]
pub struct BindRequest {
pub version: i64,
pub name: String,
pub simple_password: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SearchRequest {
pub base: String,
pub scope: Scope,
pub size_limit: i64,
pub filter: Filter,
pub attributes: Vec<String>,
pub types_only: bool,
}
#[derive(Debug, Clone)]
pub struct LdapMessage {
pub message_id: i64,
pub op: LdapOp,
}
impl LdapMessage {
pub fn parse(buf: &[u8]) -> Result<(Self, usize), BerError> {
let (tag, content, rest) = parse_tlv(buf)?;
let consumed = buf.len() - rest.len();
let msg = Self::parse_body(tag, content).map_err(BerError::into_inner_malformed)?;
Ok((msg, consumed))
}
fn parse_body(tag: u8, content: &[u8]) -> Result<Self, BerError> {
if tag != 0x30 {
return Err(BerError("LDAPMessage is not a SEQUENCE"));
}
let mut children = Children::new(content);
let (id_tag, id_content) = children.next().ok_or(BerError("no messageID"))??;
if id_tag != 0x02 {
return Err(BerError("messageID not INTEGER"));
}
let message_id = parse_integer(id_content)?;
let (op_tag, op_content) = children.next().ok_or(BerError("no protocolOp"))??;
let op = match op_tag {
TAG_BIND_REQUEST => LdapOp::Bind(parse_bind(op_content)?),
TAG_SEARCH_REQUEST => LdapOp::Search(parse_search(op_content)?),
TAG_UNBIND_REQUEST => LdapOp::Unbind,
other => LdapOp::Unsupported(other),
};
Ok(LdapMessage { message_id, op })
}
}
fn parse_bind(content: &[u8]) -> Result<BindRequest, BerError> {
let mut c = Children::new(content);
let (_vt, vc) = c.next().ok_or(BerError("bind: no version"))??;
let version = parse_integer(vc)?;
let (_nt, nc) = c.next().ok_or(BerError("bind: no name"))??;
let name = String::from_utf8_lossy(nc).into_owned();
let simple_password = match c.next() {
Some(Ok((TAG_SIMPLE_AUTH, pc))) => Some(String::from_utf8_lossy(pc).into_owned()),
_ => None, };
Ok(BindRequest {
version,
name,
simple_password,
})
}
fn parse_search(content: &[u8]) -> Result<SearchRequest, BerError> {
let mut c = Children::new(content);
let (_bt, bc) = c.next().ok_or(BerError("search: no base"))??;
let base = String::from_utf8_lossy(bc).into_owned();
let (_st, sc) = c.next().ok_or(BerError("search: no scope"))??;
let scope = match parse_integer(sc)? {
0 => Scope::Base,
1 => Scope::OneLevel,
_ => Scope::Subtree,
};
let _deref = c.next().ok_or(BerError("search: no deref"))??;
let (_szt, szc) = c.next().ok_or(BerError("search: no sizeLimit"))??;
let size_limit = parse_integer(szc)?;
if size_limit < 0 {
return Err(BerError("search: negative sizeLimit"));
}
let _time = c.next().ok_or(BerError("search: no timeLimit"))??;
let (tt, tc) = c.next().ok_or(BerError("search: no typesOnly"))??;
let types_only = tt == 0x01 && parse_bool(tc).unwrap_or(false);
let (ft, fc) = c.next().ok_or(BerError("search: no filter"))??;
let filter = decode_filter(ft, fc)?;
let mut attributes = Vec::new();
if let Some(Ok((0x30, attrs_content))) = c.next() {
for child in Children::new(attrs_content) {
let (_t, ac) = child?;
attributes.push(String::from_utf8_lossy(ac).into_owned());
}
}
Ok(SearchRequest {
base,
scope,
size_limit,
filter,
attributes,
types_only,
})
}
#[derive(Debug, Clone)]
pub struct LdapResult {
pub code: i64,
pub matched_dn: String,
pub message: String,
}
impl LdapResult {
#[must_use]
pub fn success() -> Self {
Self {
code: result_code::SUCCESS,
matched_dn: String::new(),
message: String::new(),
}
}
#[must_use]
pub fn failure(code: i64, message: &str) -> Self {
Self {
code,
matched_dn: String::new(),
message: message.to_string(),
}
}
fn encode_body(&self) -> Vec<u8> {
[
encode_enumerated(self.code),
encode_octet_string(self.matched_dn.as_bytes()),
encode_octet_string(self.message.as_bytes()),
]
.concat()
}
}
fn wrap(message_id: i64, op: Vec<u8>) -> Vec<u8> {
encode_sequence(&[super::ber::encode_integer(message_id), op])
}
#[must_use]
pub fn encode_bind_response(message_id: i64, result: &LdapResult) -> Vec<u8> {
wrap(message_id, tlv(TAG_BIND_RESPONSE, &result.encode_body()))
}
#[must_use]
pub fn encode_search_result_done(message_id: i64, result: &LdapResult) -> Vec<u8> {
wrap(
message_id,
tlv(TAG_SEARCH_RESULT_DONE, &result.encode_body()),
)
}
#[must_use]
pub fn encode_search_result_entry(
message_id: i64,
dn: &str,
attributes: &[(String, Vec<String>)],
) -> Vec<u8> {
let attrs: Vec<Vec<u8>> = attributes
.iter()
.map(|(name, values)| {
let vals: Vec<Vec<u8>> = values
.iter()
.map(|v| encode_octet_string(v.as_bytes()))
.collect();
let set = tlv(0x31, &vals.concat());
encode_sequence(&[encode_octet_string(name.as_bytes()), set])
})
.collect();
let body = [encode_octet_string(dn.as_bytes()), encode_sequence(&attrs)].concat();
wrap(message_id, tlv(TAG_SEARCH_RESULT_ENTRY, &body))
}
#[cfg(test)]
mod tests {
use super::super::ber::{
Children, encode_integer, encode_octet_string, encode_sequence, parse_tlv, tlv,
};
use super::*;
#[test]
fn parses_simple_bind() {
let bind_body = [
encode_integer(3),
encode_octet_string(b"cn=admin"),
tlv(TAG_SIMPLE_AUTH, b"pw"),
]
.concat();
let msg = encode_sequence(&[encode_integer(1), tlv(TAG_BIND_REQUEST, &bind_body)]);
let (parsed, consumed) = LdapMessage::parse(&msg).unwrap();
assert_eq!(consumed, msg.len());
assert_eq!(parsed.message_id, 1);
match parsed.op {
LdapOp::Bind(b) => {
assert_eq!(b.version, 3);
assert_eq!(b.name, "cn=admin");
assert_eq!(b.simple_password.as_deref(), Some("pw"));
}
_ => panic!("expected bind"),
}
}
#[test]
fn parses_search_with_present_filter() {
let attrs = encode_sequence(&[encode_octet_string(b"cn"), encode_octet_string(b"mail")]);
let body = [
encode_octet_string(b"dc=x"),
encode_enumerated(2),
encode_enumerated(0),
encode_integer(0),
encode_integer(0),
tlv(0x01, &[0x00]),
tlv(0x87, b"objectClass"), attrs,
]
.concat();
let msg = encode_sequence(&[encode_integer(2), tlv(TAG_SEARCH_REQUEST, &body)]);
let (parsed, _) = LdapMessage::parse(&msg).unwrap();
assert_eq!(parsed.message_id, 2);
match parsed.op {
LdapOp::Search(s) => {
assert_eq!(s.base, "dc=x");
assert_eq!(s.scope, Scope::Subtree);
assert_eq!(s.attributes, vec!["cn", "mail"]);
assert_eq!(s.filter, Filter::Present("objectClass".into()));
}
_ => panic!("expected search"),
}
}
#[test]
fn parses_unbind() {
let msg = encode_sequence(&[encode_integer(3), tlv(TAG_UNBIND_REQUEST, &[])]);
let (parsed, _) = LdapMessage::parse(&msg).unwrap();
assert!(matches!(parsed.op, LdapOp::Unbind));
}
#[test]
fn encodes_and_reparses_result_entry() {
let entry = encode_search_result_entry(
5,
"uid=bob,dc=x",
&[
("cn".into(), vec!["Bob".into()]),
("mail".into(), vec!["b@x".into()]),
],
);
let (tag, content, _) = parse_tlv(&entry).unwrap();
assert_eq!(tag, 0x30);
let kids: Vec<_> = Children::new(content).map(Result::unwrap).collect();
assert_eq!(kids[0].0, 0x02); assert_eq!(kids[1].0, TAG_SEARCH_RESULT_ENTRY);
}
#[test]
fn encodes_bind_response_success() {
let r = encode_bind_response(1, &LdapResult::success());
let (_, content, _) = parse_tlv(&r).unwrap();
let kids: Vec<_> = Children::new(content).map(Result::unwrap).collect();
assert_eq!(kids[1].0, TAG_BIND_RESPONSE);
let body_kids: Vec<_> = Children::new(kids[1].1).map(Result::unwrap).collect();
assert_eq!(body_kids[0].0, 0x0a);
assert_eq!(parse_integer(body_kids[0].1).unwrap(), 0);
}
#[test]
fn complete_frame_with_malformed_body_is_not_incomplete() {
let buf = [0x30, 0x03, 0x05, 0x01, 0x00];
let err = LdapMessage::parse(&buf).unwrap_err();
assert!(
!err.is_incomplete(),
"complete-but-malformed frame must not read as incomplete: {err:?}"
);
}
#[test]
fn truncated_outer_frame_is_incomplete() {
let buf = [0x30, 0x0a, 0x02, 0x01];
let err = LdapMessage::parse(&buf).unwrap_err();
assert!(
err.is_incomplete(),
"short outer frame must read as incomplete: {err:?}"
);
}
}