use alloc::format;
use alloc::string::String;
use crate::ast::{Protocol, TransportProto};
use crate::reverse::ProtocolDissection;
fn is_valid_raw_str(s: &str) -> bool {
!s.contains('"')
&& s.chars().all(|c| !c.is_control())
&& (s.bytes().rev().take_while(|&b| b == b'\\').count() % 2 == 0)
}
fn parse_ber_tlv(input: &[u8]) -> Option<(u8, &[u8], &[u8])> {
if input.len() < 2 {
return None;
}
let tag = input[0];
let b1 = input[1];
let (len, hdr_len) = if (b1 & 0x80) == 0 {
(b1 as usize, 2usize)
} else {
let num_octets = (b1 & 0x7F) as usize;
if num_octets == 0 || num_octets > 4 || input.len() < 2 + num_octets {
return None;
}
let mut l = 0usize;
for &b in &input[2..2 + num_octets] {
l = (l << 8) | (b as usize);
}
(l, 2 + num_octets)
};
if input.len() < hdr_len + len {
return None;
}
Some((tag, &input[hdr_len..hdr_len + len], &input[hdr_len + len..]))
}
fn parse_ber_int(bytes: &[u8]) -> Option<i64> {
if bytes.is_empty() || bytes.len() > 8 {
return None;
}
let mut val = if (bytes[0] & 0x80) != 0 { -1i64 } else { 0i64 };
for &b in bytes {
val = (val << 8) | (b as i64);
}
Some(val)
}
pub(crate) fn dissect_ldap(
transport: TransportProto,
src_port: u16,
dst_port: u16,
payload: &[u8],
) -> Option<ProtocolDissection> {
if payload.is_empty()
|| !matches!(transport, TransportProto::Tcp | TransportProto::Udp)
|| !(matches!(dst_port, 389 | 636 | 3268 | 1389)
|| matches!(src_port, 389 | 636 | 3268 | 1389))
{
return None;
}
let (outer_tag, outer_body, residual) = parse_ber_tlv(payload)?;
if outer_tag != 0x30 {
return None;
}
let (id_tag, id_bytes, op_slice) = parse_ber_tlv(outer_body)?;
if id_tag != 0x02 {
return None;
}
let msgid = u32::try_from(parse_ber_int(id_bytes)?).ok()?;
let (op_tag, op_body, op_rem) = parse_ber_tlv(op_slice)?;
if !op_rem.is_empty() {
return None;
}
let mut tokens = String::from("ldap");
if msgid != 1 {
tokens.push_str(&format!(" msgid {msgid}"));
}
match op_tag {
0x60 => {
let (v_tag, _, rest) = parse_ber_tlv(op_body)?;
if v_tag != 0x02 {
return None;
}
let (dn_tag, dn_bytes, _) = parse_ber_tlv(rest)?;
if dn_tag != 0x04 {
return None;
}
let dn = core::str::from_utf8(dn_bytes).ok()?;
if !is_valid_raw_str(dn) {
return None;
}
if dn != "dc=corp,dc=local" {
tokens.push_str(&format!(" base_dn \"{dn}\""));
}
}
0x61 | 0x65 => {
tokens.push_str(if op_tag == 0x61 {
" bind_res"
} else {
" search_done"
});
let (rc_tag, rc_bytes, rest) = parse_ber_tlv(op_body)?;
if rc_tag != 0x0A {
return None;
}
let rc = u8::try_from(parse_ber_int(rc_bytes)?).ok()?;
let (dn_tag, dn_bytes, _) = parse_ber_tlv(rest)?;
if dn_tag != 0x04 {
return None;
}
let dn = core::str::from_utf8(dn_bytes).ok()?;
if !is_valid_raw_str(dn) {
return None;
}
if rc != 0 {
tokens.push_str(&format!(" result_code {rc}"));
}
if !dn.is_empty() {
tokens.push_str(&format!(" base_dn \"{dn}\""));
}
}
0x63 => {
tokens.push_str(" search_req");
let (dn_tag, dn_bytes, mut rest) = parse_ber_tlv(op_body)?;
if dn_tag != 0x04 {
return None;
}
let dn = core::str::from_utf8(dn_bytes).ok()?;
if !is_valid_raw_str(dn) {
return None;
}
if dn != "dc=corp,dc=local" {
tokens.push_str(&format!(" base_dn \"{dn}\""));
}
for _ in 0..5 {
let (_, _, next) = parse_ber_tlv(rest)?;
rest = next;
}
if let Some((0x87, flt_bytes, _)) = parse_ber_tlv(rest) {
let flt = core::str::from_utf8(flt_bytes).ok()?;
if !is_valid_raw_str(flt) {
return None;
}
if flt != "objectClass" {
tokens.push_str(&format!(" filter \"{flt}\""));
}
}
}
0x64 => {
tokens.push_str(" search_entry");
let (dn_tag, dn_bytes, _) = parse_ber_tlv(op_body)?;
if dn_tag != 0x04 {
return None;
}
let dn = core::str::from_utf8(dn_bytes).ok()?;
if !is_valid_raw_str(dn) {
return None;
}
if dn != "dc=corp,dc=local" {
tokens.push_str(&format!(" base_dn \"{dn}\""));
}
}
0x42 => {
tokens.push_str(" unbind_req");
}
_ => return None,
}
Some(ProtocolDissection {
protocol: Protocol::Ldap,
tokens,
residual_payload: residual.to_vec(),
explicit_transport: None,
})
}