diff --git a/.github/workflows/rust_build.yml b/.github/workflows/rust_build.yml index df146ca..deb2613 100644 --- a/.github/workflows/rust_build.yml +++ b/.github/workflows/rust_build.yml @@ -28,6 +28,9 @@ jobs: uses: dtolnay/rust-toolchain@stable - name: Setup sccache uses: mozilla-actions/sccache-action@v0.0.9 + - name: Clippy check + run: cargo clippy --workspace --all-targets -- -D warnings + - name: "Build the workspace" run: cargo build --workspace diff --git a/Cargo.toml b/Cargo.toml index 3f88a08..17ed25c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,32 +4,31 @@ lto = "thin" [workspace] resolver = "2" -members = ["proto", "client", "cli"] +members = ["lber", "proto", "client", "cli"] [workspace.package] -version = "0.7.1" +version = "0.8.0" authors = ["William Brown "] -rust-version = "1.74" -edition = "2021" +rust-version = "1.85" +edition = "2024" license = "MPL-2.0" homepage = "https://github.com/kanidm/ldap3/" repository = "https://github.com/kanidm/ldap3/" keywords = ["ldap", "protocol", "authentication", "chaos"] [workspace.dependencies] -ldap3_client = { path = "./client", version = "=0.7.1" } -ldap3_proto = { path = "./proto", version = "=0.7.1" } +ldap3_lber = { path = "./lber", version = "=0.8.0" } +ldap3_client = { path = "./client", version = "=0.8.0" } +ldap3_proto = { path = "./proto", version = "=0.8.0" } base64 = "0.22" -bytes = "^1.1.0" +bytes = "^1.10.1" clap = "^4.0" clap_complete = "^4.0" futures = "^0.3.21" futures-util = "^0.3.21" -lber = "^0.4.0" -# Must match lber -nom = "7" +nom = "7.1.3" peg = "0.8.1" ron = "^0.12.0" rpassword = "7.0.0" diff --git a/cli/build.rs b/cli/build.rs index f360092..39badcd 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -4,7 +4,7 @@ use std::env; use std::path::PathBuf; use clap::CommandFactory; -use clap_complete::{generate_to, Shell}; +use clap_complete::{Shell, generate_to}; include!("src/ldap_opt.rs"); include!("src/cldap_opt.rs"); diff --git a/cli/src/ldap.rs b/cli/src/ldap.rs index eac7fc6..3523d50 100644 --- a/cli/src/ldap.rs +++ b/cli/src/ldap.rs @@ -17,7 +17,7 @@ use ldap3_client::*; use rustls::pki_types::pem::PemObject; use tracing::*; -use base64::{engine::general_purpose, Engine as _}; +use base64::{Engine as _, engine::general_purpose}; include!("./ldap_opt.rs"); diff --git a/cli/src/lib.rs b/cli/src/lib.rs index e9774eb..9fbf1f6 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -11,9 +11,9 @@ // We allow expect since it forces good error messages at the least. #![allow(clippy::expect_used)] +use tracing_subscriber::EnvFilter; use tracing_subscriber::filter::LevelFilter; use tracing_subscriber::prelude::*; -use tracing_subscriber::EnvFilter; pub fn start_tracing(verbose: bool) { let fmt_layer = tracing_subscriber::fmt::layer().with_target(false); diff --git a/client/src/lib.rs b/client/src/lib.rs index 3837b41..321e16f 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -10,11 +10,11 @@ // We allow expect since it forces good error messages at the least. #![allow(clippy::expect_used)] -use base64::{engine::general_purpose, Engine as _}; +use base64::{Engine as _, engine::general_purpose}; use futures_util::sink::SinkExt; use futures_util::stream::StreamExt; -use ldap3_proto::proto::*; use ldap3_proto::LdapCodec; +use ldap3_proto::proto::*; use rustls_platform_verifier::ConfigVerifierExt; use serde::{Deserialize, Serialize}; use std::borrow::Borrow; @@ -26,12 +26,12 @@ use tokio::net::TcpStream; use tokio::time; use tokio_rustls::{ + TlsConnector, client::TlsStream, - rustls::client::{danger::*, ClientConfig}, - rustls::pki_types::{CertificateDer, ServerName, UnixTime}, rustls::Error as RustlsError, + rustls::client::{ClientConfig, danger::*}, + rustls::pki_types::{CertificateDer, ServerName, UnixTime}, rustls::{DigitallySignedStruct, SignatureScheme}, - TlsConnector, }; use tokio_util::codec::{FramedRead, FramedWrite}; @@ -113,12 +113,23 @@ impl fmt::Display for LdapError { LdapError::TransportWriteError => { write!(f, "An error occurred writing to the transport") } - LdapError::UnavailableCriticalExtension => write!(f, "An extension marked as critical was not available"), + LdapError::UnavailableCriticalExtension => { + write!(f, "An extension marked as critical was not available") + } LdapError::InvalidCredentials => write!(f, "Invalid DN or Password"), LdapError::InsufficentAccessRights => write!(f, "Insufficient Access"), - LdapError::UnwillingToPerform => write!(f, "Too many failures, server is unwilling to perform the operation."), - LdapError::EsyncRefreshRequired => write!(f, "An initial content sync is required. The current cookie should be considered invalid."), - LdapError::NotImplemented => write!(f, "An error occurred, but we haven't implemented code to handle this error yet.") + LdapError::UnwillingToPerform => write!( + f, + "Too many failures, server is unwilling to perform the operation." + ), + LdapError::EsyncRefreshRequired => write!( + f, + "An initial content sync is required. The current cookie should be considered invalid." + ), + LdapError::NotImplemented => write!( + f, + "An error occurred, but we haven't implemented code to handle this error yet." + ), } } } @@ -445,13 +456,19 @@ impl> LdapClientBuilder { let (r, w) = tokio::io::split(tlsstream); ( LdapWriteTransport::Tls(FramedWrite::new(w, LdapCodec::default())), - LdapReadTransport::Tls(FramedRead::new(r, LdapCodec::new(Some(max_ber_size)))), + LdapReadTransport::Tls(FramedRead::new( + r, + LdapCodec::new(Some(max_ber_size), None), + )), ) } else { let (r, w) = tokio::io::split(tcpstream); ( LdapWriteTransport::Plain(FramedWrite::new(w, LdapCodec::default())), - LdapReadTransport::Plain(FramedRead::new(r, LdapCodec::new(Some(max_ber_size)))), + LdapReadTransport::Plain(FramedRead::new( + r, + LdapCodec::new(Some(max_ber_size), None), + )), ) }; @@ -595,7 +612,7 @@ impl ServerCertVerifier for YoloCertValidator { /// Doesn't test the actual *build* step because that requires a live LDAP server. #[test] fn test_ldapclient_builder() { - let url = Url::parse("ldap://ldap.example.com:389").unwrap(); + let url = Url::parse("ldap://ldap.example.com:389").expect("failed to parse URL"); let client = LdapClientBuilder::new(&url).max_ber_size(Some(1234567)); assert_eq!(client.timeout, Duration::from_secs(30)); let client = client.set_timeout(Duration::from_secs(60)); diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..a4570e4 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,5 @@ +allow-expect-in-tests = true +allow-unwrap-in-tests = false +allow-panic-in-tests = true +allow-dbg-in-tests = true +allow-indexing-slicing-in-tests = true diff --git a/lber/.gitignore b/lber/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/lber/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/lber/Cargo.toml b/lber/Cargo.toml new file mode 100644 index 0000000..8480c0f --- /dev/null +++ b/lber/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ldap3_lber" +description = "An ASN.1/BER parser/encoder based on nom" + +version.workspace = true +authors.workspace = true +rust-version.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +license = "MIT" +categories = ["encoding", "parsing"] +keywords = ["ASN1", "BER", "nom"] +documentation = "https://docs.rs/ldap3_lber" + +[dependencies] +bytes.workspace = true +nom.workspace = true diff --git a/lber/LICENSE b/lber/LICENSE new file mode 100644 index 0000000..26302cf --- /dev/null +++ b/lber/LICENSE @@ -0,0 +1,24 @@ +The MIT License (MIT) + +Copyright (c) 2026 William Brown +Copyright (c) 2017 Gregor Reitzenstein +Copyright (c) 2017 Ivan Nejgebaure + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/lber/README.md b/lber/README.md new file mode 100644 index 0000000..8f1f264 --- /dev/null +++ b/lber/README.md @@ -0,0 +1,12 @@ +# LBER - lightweight BER + +ASN.1/BER implementation based on nom. + +Forked from [lber](https://crates.io/crates/lber), not intended to be a generic ASN.1 solution. + +Future versions will probably use Serde to make encoding and (especially) +decoding less of a chore. + +# License + +Licensed under the [MIT license](LICENSE). diff --git a/lber/src/common.rs b/lber/src/common.rs new file mode 100644 index 0000000..48ee2cd --- /dev/null +++ b/lber/src/common.rs @@ -0,0 +1,36 @@ +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum TagStructure { + Primitive = 0, + Constructed = 1, +} + +impl TagStructure { + pub fn from_u8(n: u8) -> Option { + match n { + 0 => Some(TagStructure::Primitive), + 1 => Some(TagStructure::Constructed), + _ => None, + } + } +} + +/// Possible classes for a tag. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum TagClass { + Universal = 0, + Application = 1, + Context = 2, + Private = 3, +} + +impl TagClass { + pub fn from_u8(n: u8) -> Option { + match n { + 0 => Some(TagClass::Universal), + 1 => Some(TagClass::Application), + 2 => Some(TagClass::Context), + 3 => Some(TagClass::Private), + _ => None, + } + } +} diff --git a/lber/src/lib.rs b/lber/src/lib.rs new file mode 100644 index 0000000..ef931b4 --- /dev/null +++ b/lber/src/lib.rs @@ -0,0 +1,12 @@ +extern crate bytes; +extern crate nom; + +pub mod common; +pub mod parse; +pub mod structure; +pub mod structures; +pub mod universal; +pub mod write; + +pub use nom::{Err, IResult}; +pub use parse::Parser; diff --git a/lber/src/parse.rs b/lber/src/parse.rs new file mode 100644 index 0000000..e85ed6c --- /dev/null +++ b/lber/src/parse.rs @@ -0,0 +1,231 @@ +use crate::common::TagClass; +use crate::common::TagStructure; +use crate::structure::{PL, StructureTag}; +use nom; +use nom::bits::streaming as bits; +use nom::bytes::streaming::take; +use nom::combinator::map_opt; +use nom::error::{Error, ErrorKind, ParseError}; +use nom::number::streaming as number; +use nom::sequence::tuple; +use nom::{IResult, InputLength, Needed}; +use std::convert::TryFrom; + +pub const DEFAULT_MAX_BER_DEPTH: usize = 128; + +fn class_bits(i: (&[u8], usize)) -> nom::IResult<(&[u8], usize), TagClass> { + map_opt(bits::take(2usize), TagClass::from_u8)(i) +} + +fn pc_bit(i: (&[u8], usize)) -> nom::IResult<(&[u8], usize), TagStructure> { + map_opt(bits::take(1usize), TagStructure::from_u8)(i) +} + +fn tagnr_bits(i: (&[u8], usize)) -> nom::IResult<(&[u8], usize), u64> { + bits::take(5usize)(i) +} + +fn parse_type_header(i: &[u8]) -> nom::IResult<&[u8], (TagClass, TagStructure, u64)> { + nom::bits(tuple((class_bits, pc_bit, tagnr_bits)))(i) +} + +fn parse_length(i: &[u8]) -> nom::IResult<&[u8], usize> { + let (i, len) = number::be_u8(i)?; + if len < 128 { + Ok((i, len as usize)) + } else { + let len = len - 128; + let (i, b) = take(len)(i)?; + let (_, len) = parse_uint(b)?; + Ok(( + i, + usize::try_from(len) + .map_err(|_| nom::Err::Failure(Error::from_error_kind(i, ErrorKind::TooLarge)))?, + )) + } +} + +/// Extract an unsigned integer value from BER data. +pub fn parse_uint(i: &[u8]) -> nom::IResult<&[u8], u64> { + Ok((i, i.iter().fold(0, |res, &byte| (res << 8) | byte as u64))) +} + +/// Parse raw BER data into a serializable structure. +pub fn parse_tag(i: &[u8], remaining_depth: usize) -> nom::IResult<&[u8], StructureTag> { + let remaining_depth = remaining_depth.saturating_sub(1); + if remaining_depth == 0 { + return Err(nom::Err::Failure(Error::from_error_kind( + i, + ErrorKind::TooLarge, + ))); + } + + let (mut i, ((class, structure, id), len)) = tuple((parse_type_header, parse_length))(i)?; + + let pl: PL = match structure { + TagStructure::Primitive => { + let (j, content) = take(len)(i)?; + i = j; + + PL::P(content.to_vec()) + } + TagStructure::Constructed => { + let (j, mut content) = take(len)(i)?; + i = j; + + let mut tv: Vec = Vec::new(); + while content.input_len() > 0 { + let (j, sub) = parse_tag(content, remaining_depth)?; + content = j; + tv.push(sub); + } + + PL::C(tv) + } + }; + + Ok(( + i, + StructureTag { + class, + id, + payload: pl, + }, + )) +} + +pub struct Parser { + max_depth: usize, +} + +impl Parser { + pub fn new(max_depth: usize) -> Self { + Self { max_depth } + } + + pub fn parse<'a>( + &mut self, + input: &'a [u8], + ) -> IResult<&'a [u8], StructureTag, nom::error::Error<&'a [u8]>> { + if input.is_empty() { + return Err(nom::Err::Incomplete(Needed::Unknown)); + }; + parse_tag(input, self.max_depth) + } +} + +impl Default for Parser { + fn default() -> Self { + Self { + max_depth: DEFAULT_MAX_BER_DEPTH, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::common::TagClass; + use crate::structure::{PL, StructureTag}; + + const DEFAULT_TEST_BER_LIMIT: usize = 16; + + #[test] + fn test_primitive() { + let bytes: Vec = vec![2, 2, 255, 127]; + let result_tag = StructureTag { + class: TagClass::Universal, + id: 2u64, + payload: PL::P(vec![255, 127]), + }; + let rest_tag: Vec = vec![]; + + let tag = parse_tag(&bytes[..], DEFAULT_TEST_BER_LIMIT); + + assert_eq!(tag, Ok((&rest_tag[..], result_tag))); + } + + #[test] + fn test_constructed() { + let bytes: Vec = vec![ + 48, 14, 12, 12, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + ]; + let result_tag = StructureTag { + class: TagClass::Universal, + id: 16u64, + payload: PL::C(vec![StructureTag { + class: TagClass::Universal, + id: 12u64, + payload: PL::P(vec![72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]), + }]), + }; + let rest_tag: Vec = vec![]; + + let tag = parse_tag(&bytes[..], DEFAULT_TEST_BER_LIMIT); + + assert_eq!(tag, Ok((&rest_tag[..], result_tag))); + } + + #[test] + fn test_long_length() { + let bytes: Vec = vec![ + 0x30, 0x82, 0x01, 0x01, 0x80, 0x0C, 0x4A, 0x75, 0x73, 0x74, 0x41, 0x4C, 0x6F, 0x6E, + 0x67, 0x54, 0x61, 0x67, 0x81, 0x81, 0xF0, 0x4A, 0x75, 0x73, 0x74, 0x41, 0x4C, 0x6F, + 0x6E, 0x67, 0x54, 0x61, 0x67, 0x4A, 0x75, 0x73, 0x74, 0x41, 0x4C, 0x6F, 0x6E, 0x67, + 0x54, 0x61, 0x67, 0x4A, 0x75, 0x73, 0x74, 0x41, 0x4C, 0x6F, 0x6E, 0x67, 0x54, 0x61, + 0x67, 0x4A, 0x75, 0x73, 0x74, 0x41, 0x4C, 0x6F, 0x6E, 0x67, 0x54, 0x61, 0x67, 0x4A, + 0x75, 0x73, 0x74, 0x41, 0x4C, 0x6F, 0x6E, 0x67, 0x54, 0x61, 0x67, 0x4A, 0x75, 0x73, + 0x74, 0x41, 0x4C, 0x6F, 0x6E, 0x67, 0x54, 0x61, 0x67, 0x4A, 0x75, 0x73, 0x74, 0x41, + 0x4C, 0x6F, 0x6E, 0x67, 0x54, 0x61, 0x67, 0x4A, 0x75, 0x73, 0x74, 0x41, 0x4C, 0x6F, + 0x6E, 0x67, 0x54, 0x61, 0x67, 0x4A, 0x75, 0x73, 0x74, 0x41, 0x4C, 0x6F, 0x6E, 0x67, + 0x54, 0x61, 0x67, 0x4A, 0x75, 0x73, 0x74, 0x41, 0x4C, 0x6F, 0x6E, 0x67, 0x54, 0x61, + 0x67, 0x4A, 0x75, 0x73, 0x74, 0x41, 0x4C, 0x6F, 0x6E, 0x67, 0x54, 0x61, 0x67, 0x4A, + 0x75, 0x73, 0x74, 0x41, 0x4C, 0x6F, 0x6E, 0x67, 0x54, 0x61, 0x67, 0x4A, 0x75, 0x73, + 0x74, 0x41, 0x4C, 0x6F, 0x6E, 0x67, 0x54, 0x61, 0x67, 0x4A, 0x75, 0x73, 0x74, 0x41, + 0x4C, 0x6F, 0x6E, 0x67, 0x54, 0x61, 0x67, 0x4A, 0x75, 0x73, 0x74, 0x41, 0x4C, 0x6F, + 0x6E, 0x67, 0x54, 0x61, 0x67, 0x4A, 0x75, 0x73, 0x74, 0x41, 0x4C, 0x6F, 0x6E, 0x67, + 0x54, 0x61, 0x67, 0x4A, 0x75, 0x73, 0x74, 0x41, 0x4C, 0x6F, 0x6E, 0x67, 0x54, 0x61, + 0x67, 0x4A, 0x75, 0x73, 0x74, 0x41, 0x4C, 0x6F, 0x6E, 0x67, 0x54, 0x61, 0x67, 0x4A, + 0x75, 0x73, 0x74, 0x41, 0x4C, 0x6F, 0x6E, 0x67, 0x54, 0x61, 0x67, 0x4A, 0x75, 0x73, + 0x74, 0x41, 0x4C, 0x6F, 0x6E, 0x67, 0x54, 0x61, 0x67, + ]; + + let result_tag = StructureTag { + class: TagClass::Universal, + id: 16u64, + payload: PL::C(vec![ + StructureTag { + class: TagClass::Context, + id: 0, + payload: PL::P(vec![74, 117, 115, 116, 65, 76, 111, 110, 103, 84, 97, 103]), + }, + StructureTag { + class: TagClass::Context, + id: 1, + payload: PL::P(vec![ + 74, 117, 115, 116, 65, 76, 111, 110, 103, 84, 97, 103, 74, 117, 115, 116, + 65, 76, 111, 110, 103, 84, 97, 103, 74, 117, 115, 116, 65, 76, 111, 110, + 103, 84, 97, 103, 74, 117, 115, 116, 65, 76, 111, 110, 103, 84, 97, 103, + 74, 117, 115, 116, 65, 76, 111, 110, 103, 84, 97, 103, 74, 117, 115, 116, + 65, 76, 111, 110, 103, 84, 97, 103, 74, 117, 115, 116, 65, 76, 111, 110, + 103, 84, 97, 103, 74, 117, 115, 116, 65, 76, 111, 110, 103, 84, 97, 103, + 74, 117, 115, 116, 65, 76, 111, 110, 103, 84, 97, 103, 74, 117, 115, 116, + 65, 76, 111, 110, 103, 84, 97, 103, 74, 117, 115, 116, 65, 76, 111, 110, + 103, 84, 97, 103, 74, 117, 115, 116, 65, 76, 111, 110, 103, 84, 97, 103, + 74, 117, 115, 116, 65, 76, 111, 110, 103, 84, 97, 103, 74, 117, 115, 116, + 65, 76, 111, 110, 103, 84, 97, 103, 74, 117, 115, 116, 65, 76, 111, 110, + 103, 84, 97, 103, 74, 117, 115, 116, 65, 76, 111, 110, 103, 84, 97, 103, + 74, 117, 115, 116, 65, 76, 111, 110, 103, 84, 97, 103, 74, 117, 115, 116, + 65, 76, 111, 110, 103, 84, 97, 103, 74, 117, 115, 116, 65, 76, 111, 110, + 103, 84, 97, 103, 74, 117, 115, 116, 65, 76, 111, 110, 103, 84, 97, 103, + ]), + }, + ]), + }; + + let rest_tag = Vec::new(); + + let tag = parse_tag(&bytes[..], DEFAULT_TEST_BER_LIMIT); + assert_eq!(tag, Ok((&rest_tag[..], result_tag))); + } +} diff --git a/lber/src/specific.rs b/lber/src/specific.rs new file mode 100644 index 0000000..2a7e88a --- /dev/null +++ b/lber/src/specific.rs @@ -0,0 +1,59 @@ +use common::{TagClass, TagStructure}; +use traits::{BERPayload, BERTag}; + +use structure::StructureTag; + +use parse::{parse_length, parse_type_header}; + +use write::{write_length, write_type}; + +pub struct SpecificTag { + class: TagClass, + id: u64, + structure: TagStructure, + inner: T, +} + +impl SpecificTag { + pub fn wrap(class: TagClass, id: u64, structure: TagStructure, inner: T) -> Self { + SpecificTag { + class: class, + id: id, + structure: structure, + inner: inner, + } + } +} + +struct Something { + a: u32, + b: u32, +} + +impl Something { + fn fill(tag: StructureTag) -> Option { + if let Some(i) = tag + .match_class(TagClass::Application) + .and_then(|x| x.match_id(42u64)) + { + if let Some(mut j) = i.expect_constructed() { + let b = j + .pop() + .expect("failed to get b") + .parse::() + .expect("failed to parse b"); + let a = j + .pop() + .expect("failed to get a") + .parse::() + .expect("failed to parse a"); + + None + } else { + None + } + } else { + None + } + } +} diff --git a/lber/src/structure.rs b/lber/src/structure.rs new file mode 100644 index 0000000..a9ef248 --- /dev/null +++ b/lber/src/structure.rs @@ -0,0 +1,108 @@ +use crate::common::TagClass; + +/// ASN.1 structure prepared for serialization. +#[derive(Clone, PartialEq, Debug, Eq)] +pub struct StructureTag { + pub class: TagClass, + pub id: u64, + pub payload: PL, +} + +/// Tagged value payload. +#[derive(Clone, PartialEq, Debug, Eq)] +pub enum PL { + /// Primitive value. + P(Vec), + /// Constructed value. + C(Vec), +} + +impl StructureTag { + pub fn match_class(self, class: TagClass) -> Option { + if self.class == class { + Some(self) + } else { + None + } + } + + pub fn match_id(self, id: u64) -> Option { + if self.id == id { Some(self) } else { None } + } + + pub fn expect_constructed(self) -> Option> { + match self.payload { + PL::P(_) => None, + PL::C(i) => Some(i), + } + } + + pub fn expect_primitive(self) -> Option> { + match self.payload { + PL::P(i) => Some(i), + PL::C(_) => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::common::TagClass; + + #[test] + fn expect_exact() { + let tag = StructureTag { + class: TagClass::Application, + id: 65u64, + payload: PL::C(vec![StructureTag { + class: TagClass::Universal, + id: 2u64, + payload: PL::P(vec![0x16, 0x16]), + }]), + }; + + let out = tag + .clone() + .match_class(TagClass::Application) + .and_then(|x| x.match_id(65u64)); + + assert_eq!(out, Some(tag)); + } + + #[test] + fn expect_inner() { + let tag = StructureTag { + class: TagClass::Application, + id: 65u64, + payload: PL::C(vec![ + StructureTag { + class: TagClass::Universal, + id: 2u64, + payload: PL::P(vec![0x16, 0x16]), + }, + StructureTag { + class: TagClass::Application, + id: 3u64, + payload: PL::P(vec![0x3, 0x3]), + }, + ]), + }; + + let mut subt = tag.expect_constructed().expect("should be constructed"); + + let b = subt + .pop() + .expect("failed to get b") + .match_class(TagClass::Application) + .and_then(|x| x.match_id(3)); + let a = subt + .pop() + .expect("failed to get a") + .match_class(TagClass::Universal) + .and_then(|x| x.match_id(2)); + + assert!(a.is_some()); + assert!(b.is_some()); + } +} diff --git a/lber/src/structures/boolean.rs b/lber/src/structures/boolean.rs new file mode 100644 index 0000000..ddc3ff4 --- /dev/null +++ b/lber/src/structures/boolean.rs @@ -0,0 +1,34 @@ +use crate::structure; +use crate::universal; +use std::default; + +use super::ASNTag; +use crate::common::TagClass; + +/// Boolean value. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Boolean { + pub id: u64, + pub class: TagClass, + pub inner: bool, +} + +impl ASNTag for Boolean { + fn into_structure(self) -> structure::StructureTag { + structure::StructureTag { + id: self.id, + class: self.class, + payload: structure::PL::P(if self.inner { vec![0xFF] } else { vec![0x00] }), + } + } +} + +impl default::Default for Boolean { + fn default() -> Self { + Boolean { + id: universal::Types::Boolean as u64, + class: TagClass::Universal, + inner: false, + } + } +} diff --git a/lber/src/structures/explicit.rs b/lber/src/structures/explicit.rs new file mode 100644 index 0000000..ae7e60e --- /dev/null +++ b/lber/src/structures/explicit.rs @@ -0,0 +1,23 @@ +use crate::structure; + +use super::{ASNTag, Tag}; +use crate::common::TagClass; + +/// Explicitly tagged value. +// Explicit tags don't implement Default because that just wouldn't make sense. +#[derive(Clone, Debug, PartialEq)] +pub struct ExplicitTag { + pub id: u64, + pub class: TagClass, + pub inner: Box, +} + +impl ASNTag for ExplicitTag { + fn into_structure(self) -> structure::StructureTag { + structure::StructureTag { + id: self.id, + class: self.class, + payload: structure::PL::C(vec![self.inner.into_structure()]), + } + } +} diff --git a/lber/src/structures/integer.rs b/lber/src/structures/integer.rs new file mode 100644 index 0000000..0aee335 --- /dev/null +++ b/lber/src/structures/integer.rs @@ -0,0 +1,111 @@ +use super::ASNTag; +use crate::common::TagClass; +use crate::structure; +use crate::universal; + +use std::default; + +/// Integer value. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Integer { + pub id: u64, + pub class: TagClass, + pub inner: i64, +} + +/// Integer with a different tag. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Enumerated { + pub id: u64, + pub class: TagClass, + pub inner: i64, +} + +fn i_e_into_structure(id: u64, class: TagClass, inner: i64) -> structure::StructureTag { + let mut count = 0u8; + let mut rem: i64 = if inner >= 0 { inner } else { -inner }; + while { + count += 1; + rem >>= 8; + rem > 0 + } {} + + // Ensure that the most significant bit is always 0, because BER uses signed numbers. + // We shift away all but the most significant bit and check that. + // See #21 + if inner > 0 && inner >> ((8 * count) - 1) == 1 { + count += 1; + } + + let mut count = count as usize; + let mut out: Vec = Vec::with_capacity(count); + let repr = inner.to_be_bytes(); + if count > repr.len() { + out.push(0); + count -= 1; + } + out.extend_from_slice(&repr[repr.len() - count..]); + + structure::StructureTag { + id, + class, + payload: structure::PL::P(out), + } +} + +impl ASNTag for Integer { + fn into_structure(self) -> structure::StructureTag { + i_e_into_structure(self.id, self.class, self.inner) + } +} + +impl ASNTag for Enumerated { + fn into_structure(self) -> structure::StructureTag { + i_e_into_structure(self.id, self.class, self.inner) + } +} + +impl default::Default for Integer { + fn default() -> Integer { + Integer { + id: universal::Types::Integer as u64, + class: TagClass::Universal, + inner: 0i64, + } + } +} + +impl default::Default for Enumerated { + fn default() -> Enumerated { + Enumerated { + id: universal::Types::Enumerated as u64, + class: TagClass::Universal, + inner: 0i64, + } + } +} + +#[cfg(test)] +mod test { + use super::i_e_into_structure; + + use crate::common::TagClass; + use crate::structure; + + #[test] + fn test_not_unnecessary_octets() { + // 127 can be encoded into 8 bits + let result = i_e_into_structure(2, TagClass::Universal, 127); + let correct = structure::PL::P(vec![127]); + assert_eq![result.payload, correct]; + } + + #[test] + fn test_not_positive_getting_negative() { + // 128 can be encoded cannot be encoded into a 8 bit signed number + // See #21 + let result = i_e_into_structure(2, TagClass::Universal, 128); + let correct = structure::PL::P(vec![0, 128]); + assert_eq![result.payload, correct]; + } +} diff --git a/lber/src/structures/mod.rs b/lber/src/structures/mod.rs new file mode 100644 index 0000000..3a0a597 --- /dev/null +++ b/lber/src/structures/mod.rs @@ -0,0 +1,66 @@ +use crate::structure; + +pub mod boolean; +pub mod explicit; +pub mod integer; +pub mod null; +pub mod octetstring; +pub mod sequence; + +// Reexport everything +pub use self::boolean::Boolean; +pub use self::explicit::ExplicitTag; +pub use self::integer::{Enumerated, Integer}; +pub use self::null::Null; +pub use self::octetstring::OctetString; +pub use self::sequence::{Sequence, SequenceOf, Set, SetOf}; + +/// Conversion of a tag into a serializable form. +pub trait ASNTag { + /// Encode yourself into a generic Tag format. + /// + /// The only thing that changes between types is how to encode the wrapped value into bytes; + /// the encoding of the class and id does not change. By first converting the tag into + /// a more generic tag (with already encoded payload), we don't have to reimplement the + /// encoding step for class/id every time. + fn into_structure(self) -> structure::StructureTag; +} + +#[derive(Clone, Debug, PartialEq)] +/// Set of basic ASN.1 types used by LDAP. +pub enum Tag { + /// Integer value. + Integer(integer::Integer), + /// Integer with a different tag. + Enumerated(integer::Enumerated), + /// Sequence of values. + Sequence(sequence::Sequence), + /// Set of values; doesn't allow duplicates. + Set(sequence::Set), + /// String of bytes. + OctetString(octetstring::OctetString), + /// Boolean value. + Boolean(boolean::Boolean), + /// Null value. + Null(null::Null), + /// Explicitly tagged value. LDAP uses implicit tagging, but external structures might not. + ExplicitTag(explicit::ExplicitTag), + /// Serializable value. + StructureTag(structure::StructureTag), +} + +impl ASNTag for Tag { + fn into_structure(self) -> structure::StructureTag { + match self { + Tag::Integer(i) => i.into_structure(), + Tag::Enumerated(i) => i.into_structure(), + Tag::Sequence(i) => i.into_structure(), + Tag::Set(i) => i.into_structure(), + Tag::OctetString(i) => i.into_structure(), + Tag::Boolean(i) => i.into_structure(), + Tag::Null(i) => i.into_structure(), + Tag::ExplicitTag(i) => i.into_structure(), + Tag::StructureTag(s) => s, + } + } +} diff --git a/lber/src/structures/null.rs b/lber/src/structures/null.rs new file mode 100644 index 0000000..a4836d8 --- /dev/null +++ b/lber/src/structures/null.rs @@ -0,0 +1,34 @@ +use crate::structure; +use crate::universal; +use std::default; + +use super::ASNTag; +use crate::common::TagClass; + +/// Null value. +#[derive(Clone, Debug, PartialEq)] +pub struct Null { + pub id: u64, + pub class: TagClass, + pub inner: (), +} + +impl ASNTag for Null { + fn into_structure(self) -> structure::StructureTag { + structure::StructureTag { + id: self.id, + class: self.class, + payload: structure::PL::P(Vec::new()), + } + } +} + +impl default::Default for Null { + fn default() -> Self { + Null { + id: universal::Types::Null as u64, + class: TagClass::Universal, + inner: (), + } + } +} diff --git a/lber/src/structures/octetstring.rs b/lber/src/structures/octetstring.rs new file mode 100644 index 0000000..8f6a1b7 --- /dev/null +++ b/lber/src/structures/octetstring.rs @@ -0,0 +1,34 @@ +use crate::structure; +use crate::universal; +use std::default; + +use super::ASNTag; +use crate::common::TagClass; + +/// String of bytes. +#[derive(Clone, Debug, PartialEq)] +pub struct OctetString { + pub id: u64, + pub class: TagClass, + pub inner: Vec, +} + +impl ASNTag for OctetString { + fn into_structure(self) -> structure::StructureTag { + structure::StructureTag { + id: self.id, + class: self.class, + payload: structure::PL::P(self.inner), + } + } +} + +impl default::Default for OctetString { + fn default() -> Self { + OctetString { + id: universal::Types::OctetString as u64, + class: TagClass::Universal, + inner: Vec::new(), + } + } +} diff --git a/lber/src/structures/sequence.rs b/lber/src/structures/sequence.rs new file mode 100644 index 0000000..c1de1be --- /dev/null +++ b/lber/src/structures/sequence.rs @@ -0,0 +1,116 @@ +use crate::structure; +use crate::universal; +use std::default; + +use super::{ASNTag, Tag}; +use crate::common::TagClass; + +/// Sequence of values. +#[derive(Clone, Debug, PartialEq)] +pub struct Sequence { + pub id: u64, + pub class: TagClass, + pub inner: Vec, +} + +impl ASNTag for Sequence { + fn into_structure(self) -> structure::StructureTag { + structure::StructureTag { + id: self.id, + class: self.class, + payload: structure::PL::C(self.inner.into_iter().map(|x| x.into_structure()).collect()), + } + } +} + +impl default::Default for Sequence { + fn default() -> Self { + Sequence { + id: universal::Types::Sequence as u64, + class: TagClass::Universal, + inner: Vec::new(), + } + } +} + +/// Set of values; doesn't allow duplicates. +#[derive(Clone, Debug, PartialEq)] +pub struct Set { + pub id: u64, + pub class: TagClass, + pub inner: Vec, +} + +impl ASNTag for Set { + fn into_structure(self) -> structure::StructureTag { + structure::StructureTag { + id: self.id, + class: self.class, + payload: structure::PL::C(self.inner.into_iter().map(|x| x.into_structure()).collect()), + } + } +} + +impl default::Default for Set { + fn default() -> Self { + Set { + id: universal::Types::Set as u64, + class: TagClass::Universal, + inner: Vec::new(), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SequenceOf { + pub id: u64, + pub class: TagClass, + pub inner: Vec, +} + +impl ASNTag for SequenceOf { + fn into_structure(self) -> structure::StructureTag { + structure::StructureTag { + id: self.id, + class: self.class, + payload: structure::PL::C(self.inner.into_iter().map(|x| x.into_structure()).collect()), + } + } +} + +impl default::Default for SequenceOf { + fn default() -> Self { + SequenceOf:: { + id: universal::Types::Sequence as u64, + class: TagClass::Universal, + inner: Vec::new(), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct SetOf { + pub id: u64, + pub class: TagClass, + pub inner: Vec, +} + +impl ASNTag for SetOf { + fn into_structure(self) -> structure::StructureTag { + structure::StructureTag { + id: self.id, + class: self.class, + payload: structure::PL::C(self.inner.into_iter().map(|x| x.into_structure()).collect()), + } + } +} + +impl default::Default for SetOf { + fn default() -> Self { + SetOf { + id: universal::Types::Set as u64, + class: TagClass::Universal, + inner: Vec::new(), + } + } +} diff --git a/lber/src/traits/mod.rs b/lber/src/traits/mod.rs new file mode 100644 index 0000000..6542edd --- /dev/null +++ b/lber/src/traits/mod.rs @@ -0,0 +1,36 @@ +pub trait AsBER : Sized { + fn encode_into(&self, &mut Vec); + fn encode(&self) -> Vec { + let mut out: Vec = Vec::new(); + self.encode_into(&mut out); + return out; + } + + fn decode(&[u8]) -> Option; + + fn len(&self) -> u64; +} + +pub trait BERPayload : Sized { + fn encode_into(&self, &mut Vec); + fn encode(&self) -> Vec { + let mut out: Vec = Vec::new(); + self.encode_into(&mut out); + return out; + } + + fn decode(&[u8]) -> Option; + + fn len(&self) -> u64; +} + +pub trait BERTag : Sized { + fn encode_into(&self, &mut Vec); + fn encode(&self) -> Vec { + let mut out: Vec = Vec::new(); + self.encode_into(&mut out); + return out; + } + + fn decode(&[u8]) -> Option; +} diff --git a/lber/src/universal.rs b/lber/src/universal.rs new file mode 100644 index 0000000..db42094 --- /dev/null +++ b/lber/src/universal.rs @@ -0,0 +1,32 @@ +/// Universal types. +pub enum Types { + Eoc = 0, + Boolean = 1, + Integer = 2, + BitString = 3, + OctetString = 4, + Null = 5, + ObjectIdentifier = 6, + ObjectDescriptor = 7, + External = 8, + Real = 9, + Enumerated = 10, + EmbeddedPdv = 11, + Utf8String = 12, + RelativeOid = 13, + Sequence = 16, + Set = 17, + NumericString = 18, + PrintableString = 19, + T61String = 20, + VideotexString = 21, + Ia5String = 22, + UtcTime = 23, + GeneralizedTime = 24, + GraphicString = 25, + VisibleString = 26, + GeneralString = 27, + UniversalString = 28, + CharacterString = 29, + BmpString = 30, +} diff --git a/lber/src/write.rs b/lber/src/write.rs new file mode 100644 index 0000000..765c22c --- /dev/null +++ b/lber/src/write.rs @@ -0,0 +1,203 @@ +//! BER encoding support. +use crate::common::{TagClass, TagStructure}; +use crate::structure::{PL, StructureTag}; +use bytes::BytesMut; + +use std::io::{self, Write}; + +/// BER-encode a tag structure into the provided buffer. +pub fn encode_into(buf: &mut BytesMut, tag: StructureTag) -> io::Result<()> { + let mut tag_vec = Vec::new(); + encode_inner(&mut tag_vec, tag)?; + buf.extend(tag_vec); + Ok(()) +} + +fn encode_inner(buf: &mut Vec, tag: StructureTag) -> io::Result<()> { + let structure = match tag.payload { + PL::P(_) => TagStructure::Primitive, + PL::C(_) => TagStructure::Constructed, + }; + + write_type(buf, tag.class, structure, tag.id); + match tag.payload { + PL::P(v) => { + write_length(buf, v.len()); + buf.extend(v); + } + PL::C(tags) => { + let mut tmp = Vec::new(); + for tag in tags { + encode_inner(&mut tmp, tag)?; + } + write_length(buf, tmp.len()); + buf.extend(tmp); + } + }; + + Ok(()) +} + +fn write_type(w: &mut dyn Write, class: TagClass, structure: TagStructure, id: u64) { + let extended_tag: Option>; + + let type_byte = { + // First two bits: Class + (class as u8) << 6 | + // Bit 6: Primitive/Constructed + (structure as u8) << 5 | + // Bit 5-1: Tag Number + if id > 30 + { + let mut tagbytes: Vec = Vec::new(); + + let mut tag = id; + while tag > 0 + { + // Only take the 7 lower bits. + let byte = (tag & 0x7F) as u8; + + tag >>= 7; + + tagbytes.push(byte); + } + + extended_tag = Some(tagbytes); + + // This means we need to set the 5 tag bits to 11111, so 31 or 0x1F + 0x1F + } + else + { + extended_tag = None; + id as u8 + } + }; // let type_byte + + let _ = w.write(&[type_byte]); + + if let Some(mut ext_bytes) = extended_tag { + for _ in 0..ext_bytes.len() - 1 { + let mut byte = ext_bytes.pop().expect("should have at least one byte"); + + // Set the first bit + byte |= 0x80; + + let _ = w.write(&[byte]); + } + + let byte = ext_bytes.pop().expect("should have at least one byte"); + let _ = w.write(&[byte]); + } +} + +// Yes I know you could overflow the length in theory. But, do you have 2^64 bytes of memory? +fn write_length(w: &mut dyn Write, length: usize) { + // Short form + if length < 128 { + let _ = w.write(&[length as u8]); + } + // Long form + else { + let mut count = 0u8; + let mut len = length; + while { + count += 1; + len >>= 8; + len > 0 + } {} + + let _ = w.write(&[count | 0x80]); + let repr = &length.to_be_bytes(); + let len = repr.len(); + let bytes = &repr[(len - (count as usize))..]; + let _ = w.write_all(bytes); + } +} + +#[cfg(test)] +mod tests { + use std::default::Default; + + use bytes::BytesMut; + + use crate::common::TagClass::*; + use crate::structures::*; + + #[test] + fn encode_simple_tag() { + let tag = Tag::Integer(Integer { + inner: 1616, + ..Default::default() + }); + + let mut buf = BytesMut::new(); + super::encode_into(&mut buf, tag.into_structure()).expect("failed to encode tag"); + + assert_eq!(buf, vec![0x2, 0x2, 0x06, 0x50]); + } + + #[test] + fn encode_constructed_tag() { + let tag = Tag::Sequence(Sequence { + inner: vec![Tag::OctetString(OctetString { + inner: String::from("Hello World!").into_bytes(), + ..Default::default() + })], + ..Default::default() + }); + + let mut buf = BytesMut::new(); + super::encode_into(&mut buf, tag.into_structure()).expect("failed to encode tag"); + + assert_eq!( + buf, + vec![ + 48, 14, 4, 12, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 + ] + ); + } + + #[test] + fn complex_tag() { + let tag = Tag::Sequence(Sequence { + inner: vec![ + Tag::Integer(Integer { + inner: 1, + ..Default::default() + }), + Tag::Sequence(Sequence { + id: 0, + class: Application, + inner: vec![ + Tag::Integer(Integer { + inner: 3, + ..Default::default() + }), + Tag::OctetString(OctetString { + inner: String::from("cn=root,dc=plabs").into_bytes(), + ..Default::default() + }), + Tag::OctetString(OctetString { + id: 0, + class: Context, + inner: String::from("asdf").into_bytes(), + }), + ], + }), + ], + ..Default::default() + }); + + let expected = vec![ + 0x30, 0x20, 0x02, 0x01, 0x01, 0x60, 0x1B, 0x02, 0x01, 0x03, 0x04, 0x10, 0x63, 0x6e, + 0x3d, 0x72, 0x6f, 0x6f, 0x74, 0x2c, 0x64, 0x63, 0x3d, 0x70, 0x6c, 0x61, 0x62, 0x73, + 0x80, 0x04, 0x61, 0x73, 0x64, 0x66, + ]; + + let mut buf = BytesMut::new(); + super::encode_into(&mut buf, tag.into_structure()).expect("failed to encode tag"); + + assert_eq!(buf, expected); + } +} diff --git a/proto/Cargo.toml b/proto/Cargo.toml index 2057248..a47eae5 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -18,7 +18,7 @@ serde = ["dep:serde", "uuid/serde"] [dependencies] base64.workspace = true bytes.workspace = true -lber.workspace = true +ldap3_lber.workspace = true nom.workspace = true peg.workspace = true tokio-util = { workspace = true, features = ["codec"] } diff --git a/proto/examples/sasltest/main.rs b/proto/examples/sasltest/main.rs index b76ec0a..462f487 100644 --- a/proto/examples/sasltest/main.rs +++ b/proto/examples/sasltest/main.rs @@ -19,8 +19,8 @@ use tracing::Level; use futures_util::sink::SinkExt; use futures_util::stream::StreamExt; -use ldap3_proto::proto::*; use ldap3_proto::LdapCodec; +use ldap3_proto::proto::*; #[tokio::main] async fn main() -> Result<(), Box> { @@ -29,9 +29,10 @@ async fn main() -> Result<(), Box> { .finish(); tracing::subscriber::set_global_default(subs).expect("setting default subscriber failed"); - let ldap_password = env::var("LDAP_PASSWORD").unwrap(); // password - let ldap_server_addr = env::var("LDAP_SERVER_ADDR").unwrap(); // domain.com:port - let ldap_username = env::var("LDAP_USERNAME").unwrap(); // username@domain + let ldap_password = env::var("LDAP_PASSWORD").expect("LDAP_PASSWORD not set"); // password + let ldap_server_addr = env::var("LDAP_SERVER_ADDR").expect("LDAP_SERVER_ADDR not set"); // domain.com:port + let ldap_username = env::var("LDAP_USERNAME").expect("LDAP_USERNAME not set"); // username@domain + #[allow(clippy::expect_used, clippy::expect_fun_call)] let addr = SocketAddr::from_str(&ldap_server_addr).expect(&format!( "Unable to parse address, addr is {:?}", &ldap_server_addr @@ -42,7 +43,7 @@ async fn main() -> Result<(), Box> { let mut framed = Framed::new(tcpstream, LdapCodec::default()); let mut ntlm = AuthProvier::new(&ldap_username, &ldap_password); - let ntlm_token = ntlm.step(&[]).unwrap(); + let ntlm_token = ntlm.step(&[]).expect("failed to generate initial token"); let msg = LdapMsg { msgid: 1, @@ -67,7 +68,8 @@ async fn main() -> Result<(), Box> { } LdapResultCode::SaslBindInProgress => { if let Some(ref cred) = res.saslcreds { - let ntlm_token = ntlm.step(cred).unwrap(); + let ntlm_token = + ntlm.step(cred).expect("failed to generate NTLM token"); let msg = LdapMsg { msgid: 2, op: LdapOp::BindRequest(LdapBindRequest { @@ -80,7 +82,7 @@ async fn main() -> Result<(), Box> { ctrl: vec![], }; - let _ = framed.send(msg).await?; + framed.send(msg).await?; } } _ => { @@ -102,7 +104,7 @@ struct AuthProvier { impl AuthProvier { fn new(ldap_username: &str, ldap_password: &str) -> Self { let identity = AuthIdentity { - username: Username::parse(ldap_username).unwrap(), + username: Username::parse(ldap_username).expect("failed to parse LDAP username"), password: ldap_password.to_string().into(), }; @@ -113,7 +115,7 @@ impl AuthProvier { .with_credential_use(CredentialUse::Outbound) .with_auth_data(&identity) .execute(&mut ntlm) - .unwrap(); + .expect("failed to acquire credentials handle"); Self { ntlm, diff --git a/proto/examples/search/main.rs b/proto/examples/search/main.rs index 3f45b86..520104b 100644 --- a/proto/examples/search/main.rs +++ b/proto/examples/search/main.rs @@ -1,87 +1,86 @@ -use std::{env, net::SocketAddr, str::FromStr, vec}; - -use futures_util::{SinkExt, StreamExt}; -use ldap3_proto::{ - parse_ldap_filter_str, - proto::{LdapBindCred, LdapBindRequest, LdapOp, LdapSearchRequest}, - LdapCodec, LdapMsg, LdapResultCode, -}; -use tokio::net::TcpStream; -use tokio_util::codec::Framed; -use tracing::{info, Level}; - -#[tokio::main] -pub async fn main() -> Result<(), Box> { - let subs = tracing_subscriber::FmtSubscriber::builder() - .with_max_level(Level::DEBUG) - .finish(); - tracing::subscriber::set_global_default(subs).expect("setting default subscriber failed"); - - let ldap_password = env::var("LDAP_PASSWORD").unwrap(); // password - let ldap_server_addr = env::var("LDAP_SERVER_ADDR").unwrap(); // domain.com:port - let ldap_username_dn = env::var("LDAP_USERNAME_DN").unwrap(); // username@domain - let addr = SocketAddr::from_str(&ldap_server_addr) - .unwrap_or_else(|_| panic!("Unable to parse address, addr is {:?}", &ldap_server_addr)); - - let tcpstream = TcpStream::connect(addr).await?; - - let mut framed = Framed::new(tcpstream, LdapCodec::default()); - - let msg = LdapMsg { - msgid: 1, - op: LdapOp::BindRequest(LdapBindRequest { - dn: ldap_username_dn, - cred: LdapBindCred::Simple(ldap_password), - }), - ctrl: vec![], - }; - - framed.send(msg).await?; - loop { - if let Some(Ok(msg)) = framed.next().await { - if let LdapOp::BindResponse(res) = msg.op { - match res.res.code { - LdapResultCode::Success => { - info!("Bind successful"); - break; - } - _ => { - panic!("Bind failed: {:?}", res) - } - } - } - } else { - panic!("Unable to get bind response") - } - } - - let filter = parse_ldap_filter_str("(cn=*Info*)")?; - let search_req = LdapSearchRequest { - attrs: vec!["cn".to_string()], - base: "CN=Schema,CN=Configuration,DC=example,DC=com".to_string(), - filter, - scope: ldap3_proto::LdapSearchScope::Subtree, - sizelimit: 10, - timelimit: 100, - aliases: ldap3_proto::proto::LdapDerefAliases::Never, - typesonly: true, - }; - - let msg = LdapMsg { - ctrl: vec![], - msgid: 1, - op: LdapOp::SearchRequest(search_req), - }; - - framed.send(msg).await?; - - loop { - let res = framed.next().await.expect("no meg")?; - if let LdapOp::SearchResultDone(..) = &res.op { - info!("search successful"); - break; - } - } - - Ok(()) -} +use std::{env, net::SocketAddr, str::FromStr, vec}; + +use futures_util::{SinkExt, StreamExt}; +use ldap3_proto::{ + LdapCodec, LdapMsg, LdapResultCode, parse_ldap_filter_str, + proto::{LdapBindCred, LdapBindRequest, LdapOp, LdapSearchRequest}, +}; +use tokio::net::TcpStream; +use tokio_util::codec::Framed; +use tracing::{Level, info}; + +#[tokio::main] +pub async fn main() -> Result<(), Box> { + let subs = tracing_subscriber::FmtSubscriber::builder() + .with_max_level(Level::DEBUG) + .finish(); + tracing::subscriber::set_global_default(subs).expect("setting default subscriber failed"); + + let ldap_password = env::var("LDAP_PASSWORD").expect("LDAP_PASSWORD not set"); // password + let ldap_server_addr = env::var("LDAP_SERVER_ADDR").expect("LDAP_SERVER_ADDR not set"); // domain.com:port + let ldap_username_dn = env::var("LDAP_USERNAME_DN").expect("LDAP_USERNAME_DN not set"); // username@domain + let addr = SocketAddr::from_str(&ldap_server_addr) + .unwrap_or_else(|_| panic!("Unable to parse address, addr is {:?}", &ldap_server_addr)); + + let tcpstream = TcpStream::connect(addr).await?; + + let mut framed = Framed::new(tcpstream, LdapCodec::default()); + + let msg = LdapMsg { + msgid: 1, + op: LdapOp::BindRequest(LdapBindRequest { + dn: ldap_username_dn, + cred: LdapBindCred::Simple(ldap_password), + }), + ctrl: vec![], + }; + + framed.send(msg).await?; + loop { + if let Some(Ok(msg)) = framed.next().await { + if let LdapOp::BindResponse(res) = msg.op { + match res.res.code { + LdapResultCode::Success => { + info!("Bind successful"); + break; + } + _ => { + panic!("Bind failed: {:?}", res) + } + } + } + } else { + panic!("Unable to get bind response") + } + } + + let filter = parse_ldap_filter_str("(cn=*Info*)")?; + let search_req = LdapSearchRequest { + attrs: vec!["cn".to_string()], + base: "CN=Schema,CN=Configuration,DC=example,DC=com".to_string(), + filter, + scope: ldap3_proto::LdapSearchScope::Subtree, + sizelimit: 10, + timelimit: 100, + aliases: ldap3_proto::proto::LdapDerefAliases::Never, + typesonly: true, + }; + + let msg = LdapMsg { + ctrl: vec![], + msgid: 1, + op: LdapOp::SearchRequest(search_req), + }; + + framed.send(msg).await?; + + loop { + let res = framed.next().await.expect("no meg")?; + if let LdapOp::SearchResultDone(..) = &res.op { + info!("search successful"); + break; + } + } + + Ok(()) +} diff --git a/proto/examples/tokio/main.rs b/proto/examples/tokio/main.rs index 73be6eb..ed7d412 100644 --- a/proto/examples/tokio/main.rs +++ b/proto/examples/tokio/main.rs @@ -10,8 +10,8 @@ use std::net; use std::str::FromStr; use tokio_util::codec::{FramedRead, FramedWrite}; -use ldap3_proto::simple::*; use ldap3_proto::LdapCodec; +use ldap3_proto::simple::*; pub struct LdapSession { dn: String, @@ -22,7 +22,7 @@ impl LdapSession { if sbr.dn == "cn=Directory Manager" && sbr.pw == "password" { self.dn = sbr.dn.to_string(); sbr.gen_success() - } else if sbr.dn == "" && sbr.pw == "" { + } else if sbr.dn.is_empty() && sbr.pw.is_empty() { self.dn = "Anonymous".to_string(); sbr.gen_success() } else { @@ -83,14 +83,11 @@ async fn handle_client(socket: TcpStream, _paddr: net::SocketAddr) { while let Some(msg) = reqs.next().await { debug!(?msg, "ldap message"); - let server_op = match msg - .map_err(|_e| ()) - .and_then(|msg| ServerOps::try_from(msg)) - { + let server_op = match msg.map_err(|_e| ()).and_then(ServerOps::try_from) { Ok(v) => v, Err(_) => { let _err = resp - .send(DisconnectionNotice::gen( + .send(DisconnectionNotice::gen_response( LdapResultCode::Other, "Internal Server Error", )) @@ -112,12 +109,12 @@ async fn handle_client(socket: TcpStream, _paddr: net::SocketAddr) { }; for rmsg in result.into_iter() { - if let Err(_) = resp.send(rmsg).await { + if resp.send(rmsg).await.is_err() { return; } } - if let Err(_) = resp.flush().await { + if resp.flush().await.is_err() { return; } } @@ -140,12 +137,18 @@ async fn acceptor(listener: Box) { #[tokio::main] async fn main() -> () { tracing_subscriber::fmt::init(); - let addr = net::SocketAddr::from_str("127.0.0.1:12345").unwrap(); - let listener = Box::new(TcpListener::bind(&addr).await.unwrap()); + let addr = net::SocketAddr::from_str("127.0.0.1:12345").expect("Unable to parse address"); + let listener = Box::new( + TcpListener::bind(&addr) + .await + .expect("Failed to bind to address"), + ); // Initiate the acceptor task. tokio::spawn(acceptor(listener)); info!("started ldap://127.0.0.1:12345 ..."); - tokio::signal::ctrl_c().await.unwrap(); + tokio::signal::ctrl_c() + .await + .expect("Failed to listen for ctrl_c signal"); } diff --git a/proto/src/control.rs b/proto/src/control.rs index 4e5d65b..565649c 100644 --- a/proto/src/control.rs +++ b/proto/src/control.rs @@ -1,1046 +1,1046 @@ -use std::fmt; - -use bytes::BytesMut; -use lber::{ - common::TagClass, - structure::StructureTag, - structures::{ASNTag, Boolean, Enumerated, Integer, OctetString, Sequence, Tag}, - universal::Types, - Parser, -}; -use uuid::Uuid; - -use crate::{ - bytes_to_string, - error::LdapProtoError, - proto::{ber_bool_to_bool, ber_integer_to_i64, SyncRequestMode, SyncStateValue}, - LdapResultCode, -}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -#[derive(Clone, PartialEq, Hash, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -pub enum LdapControl { - SyncRequest { - // Shouldn't this imply true? - criticality: bool, - mode: SyncRequestMode, - cookie: Option>, - reload_hint: bool, - }, - SyncState { - state: SyncStateValue, - entry_uuid: Uuid, - cookie: Option>, - }, - SyncDone { - cookie: Option>, - refresh_deletes: bool, - }, - AdDirsync { - flags: i64, - // Msdn and wireshark disagree on the name of this type. - max_bytes: i64, - cookie: Option>, - }, - // https://www.ietf.org/rfc/rfc2696.txt - SimplePagedResults { - size: i64, - cookie: Vec, - }, - ManageDsaIT { - criticality: bool, - }, - //1.2.840.113556.1.4.473 - ServerSort { - sort_requests: Vec, - }, - ServerSortResult { - sort_result: ServerSortResult, - }, - PasswordPolicyRequest { - criticality: bool, - }, - // 1.2.840.113556.1.4.1338 - SearchOptions { - criticality: bool, - flags: i32, - }, - // 1.2.840.113556.1.4.417 - ShowDeleted { - criticality: bool, - }, - // 1.2.840.113556.1.4.801 - SdFlags { - criticality: bool, - flags: i32, - }, - // 1.2.840.113556.1.4.529 - ExtendedDn { - criticality: bool, - format: i32, - }, - Unknown { - oid: String, - criticality: bool, - value: Option>, - }, -} - -/// Flag for `SearchOptions`: search across all naming contexts (phantom root). -pub const SEARCH_FLAG_PHANTOM_ROOT: i32 = 0x02; - -/// SD flags bitmask constants for `SdFlags` control. -pub const SD_FLAG_OWNER: i32 = 0x01; -pub const SD_FLAG_GROUP: i32 = 0x02; -pub const SD_FLAG_DACL: i32 = 0x04; -pub const SD_FLAG_SACL: i32 = 0x08; - -#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -pub struct ServerSortResult { - pub result_code: LdapResultCode, - pub attribute_type: Option, -} - -#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -pub struct ServerSortRequet { - pub attribute_name: String, - pub ordering_rule: Option, - pub reverse_order: bool, -} - -impl fmt::Debug for LdapControl { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - LdapControl::SyncRequest { - criticality, - mode, - cookie, - reload_hint, - } => { - let d_cookie = cookie.as_ref().map(|s| bytes_to_string!(s)); - f.debug_struct("LdapControl::SyncRequest") - .field("criticality", &criticality) - .field("mode", &mode) - .field("cookie", &d_cookie) - .field("reload_hint", &reload_hint) - .finish() - } - LdapControl::SyncState { - state, - entry_uuid, - cookie, - } => { - let d_cookie = cookie.as_ref().map(|s| bytes_to_string!(s)); - f.debug_struct("LdapControl::SyncState") - .field("state", &state) - .field("entry_uuid", &entry_uuid) - .field("cookie", &d_cookie) - .finish() - } - LdapControl::SyncDone { - cookie, - refresh_deletes, - } => { - let d_cookie = cookie.as_ref().map(|s| bytes_to_string!(s)); - f.debug_struct("LdapControl::SyncDone") - .field("refresh_deletes", &refresh_deletes) - .field("cookie", &d_cookie) - .finish() - } - LdapControl::AdDirsync { - flags, - max_bytes, - cookie, - } => { - let d_cookie = cookie.as_ref().map(|s| bytes_to_string!(s)); - f.debug_struct("LdapControl::AdDirsync") - .field("flags", &flags) - .field("max_bytes", &max_bytes) - .field("cookie", &d_cookie) - .finish() - } - LdapControl::SimplePagedResults { size, cookie } => { - let d_cookie = bytes_to_string!(cookie); - f.debug_struct("LdapControl::SimplePagedResults") - .field("size", &size) - .field("cookie", &d_cookie) - .finish() - } - LdapControl::ManageDsaIT { criticality } => f - .debug_struct("LdapControl::ManageDsaIT") - .field("criticality", &criticality) - .finish(), - - LdapControl::ServerSort { sort_requests } => f - .debug_struct("LdapControl::ServerSort") - .field("sort_requests", &sort_requests) - .finish(), - LdapControl::ServerSortResult { sort_result } => f - .debug_struct("LdapControl::ServerSortResult") - .field("sort_result", &sort_result) - .finish(), - LdapControl::PasswordPolicyRequest { criticality } => f - .debug_struct("LdapControl::PasswordPolicyRequest") - .field("criticality", &criticality) - .finish(), - LdapControl::SearchOptions { criticality, flags } => f - .debug_struct("LdapControl::SearchOptions") - .field("criticality", &criticality) - .field("flags", &flags) - .finish(), - LdapControl::ShowDeleted { criticality } => f - .debug_struct("LdapControl::ShowDeleted") - .field("criticality", &criticality) - .finish(), - LdapControl::SdFlags { criticality, flags } => f - .debug_struct("LdapControl::SdFlags") - .field("criticality", &criticality) - .field("flags", &flags) - .finish(), - LdapControl::ExtendedDn { - criticality, - format, - } => f - .debug_struct("LdapControl::ExtendedDn") - .field("criticality", &criticality) - .field("format", &format) - .finish(), - LdapControl::Unknown { - oid, - criticality, - value, - } => { - let d_value = value.as_ref().map(|v| bytes_to_string!(v)); - f.debug_struct("LdapControl::Unknown") - .field("oid", &oid) - .field("criticality", &criticality) - .field("value", &d_value) - .finish() - } - } - } -} - -impl TryFrom for LdapControl { - type Error = LdapProtoError; - - fn try_from(value: StructureTag) -> Result { - let mut seq = value - .match_id(Types::Sequence as u64) - .and_then(|t| t.expect_constructed()) - .ok_or(LdapProtoError::ControlBer)?; - - // We destructure in reverse order due to how vec in rust - // works. - let (oid_tag, criticality_tag, value_tag) = match seq.len() { - 1 => { - let v = None; - let c = None; - let o = seq.pop(); - (o, c, v) - } - 2 => { - let second = seq.pop(); - let o = seq.pop(); - // Distinguish criticality (Boolean, id=1) from value (OctetString, id=4) - let is_boolean = second - .as_ref() - .map(|t| t.class == TagClass::Universal && t.id == Types::Boolean as u64) - .unwrap_or(false); - if is_boolean { - (o, second, None) - } else { - (o, None, second) - } - } - 3 => { - let v = seq.pop(); - let c = seq.pop(); - let o = seq.pop(); - (o, c, v) - } - _ => return Err(LdapProtoError::ControlSeqLen), - }; - - trace!(?oid_tag, ?criticality_tag, ?value_tag); - - // We need to know what the oid is first. - let oid = oid_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(|bv| String::from_utf8(bv).ok()) - .ok_or(LdapProtoError::ControlBer)?; - - match oid.as_str() { - "1.3.6.1.4.1.4203.1.9.1.1" => { - // parse as sync req - let criticality = criticality_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Boolean as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_bool_to_bool) - .unwrap_or(false); - - let value_ber = value_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()) - .ok_or(LdapProtoError::ControlBer)?; - - let mut parser = Parser::new(); - let (_rem, value) = parser - .parse(&value_ber) - .map_err(|_| LdapProtoError::ControlBer)?; - - let mut value = value - .expect_constructed() - .ok_or(LdapProtoError::ControlBer)?; - - value.reverse(); - - let mode = value - .pop() - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Enumerated as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_integer_to_i64) - .and_then(|v| match v { - 1 => Some(SyncRequestMode::RefreshOnly), - 3 => Some(SyncRequestMode::RefreshAndPersist), - _ => None, - }) - .ok_or(LdapProtoError::ControlSyncMode)?; - - let cookie = value - .pop() - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()); - - let reload_hint = value - .pop() - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Boolean as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_bool_to_bool) - .unwrap_or(false); - - Ok(LdapControl::SyncRequest { - criticality, - mode, - cookie, - reload_hint, - }) - } - "1.3.6.1.4.1.4203.1.9.1.2" => { - // parse as sync state control - - //criticality is ignored. - - let value_ber = value_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()) - .ok_or(LdapProtoError::ControlBer)?; - - let mut parser = Parser::new(); - let (_rem, value) = parser - .parse(&value_ber) - .map_err(|_| LdapProtoError::ControlBer)?; - - let mut value = value - .expect_constructed() - .ok_or(LdapProtoError::ControlBer)?; - - value.reverse(); - - let state = value - .pop() - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Enumerated as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_integer_to_i64) - .and_then(|v| match v { - 0 => Some(SyncStateValue::Present), - 1 => Some(SyncStateValue::Add), - 2 => Some(SyncStateValue::Modify), - 3 => Some(SyncStateValue::Delete), - _ => None, - }) - .ok_or(LdapProtoError::ControlSyncState)?; - - let entry_uuid = value - .pop() - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()) - .ok_or(LdapProtoError::ControlBer) - .and_then(|v| { - Uuid::from_slice(&v).map_err(|_| LdapProtoError::ControlSyncUuid) - })?; - - let cookie = value - .pop() - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()); - - Ok(LdapControl::SyncState { - state, - entry_uuid, - cookie, - }) - } - "1.3.6.1.4.1.4203.1.9.1.3" => { - // parse as sync done control - // criticality is ignored. - - let value_ber = value_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()) - .ok_or(LdapProtoError::ControlBer)?; - - let mut parser = Parser::new(); - let (_rem, value) = parser - .parse(&value_ber) - .map_err(|_| LdapProtoError::ControlBer)?; - - let mut value = value - .expect_constructed() - .ok_or(LdapProtoError::ControlBer)?; - - value.reverse(); - - let cookie = value - .pop() - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()); - - let refresh_deletes = value - .pop() - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Boolean as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_bool_to_bool) - .unwrap_or(false); - - Ok(LdapControl::SyncDone { - cookie, - refresh_deletes, - }) - } - "1.2.840.113556.1.4.841" => { - let value_ber = value_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()) - .ok_or(LdapProtoError::ControlBer)?; - - let mut parser = Parser::new(); - let (_rem, value) = parser - .parse(&value_ber) - .map_err(|_| LdapProtoError::ControlBer)?; - - let mut value = value - .expect_constructed() - .ok_or(LdapProtoError::ControlBer)?; - - value.reverse(); - - let flags = value - .pop() - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Integer as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_integer_to_i64) - .ok_or(LdapProtoError::ControlAdDirsyncInteger)?; - - let max_bytes = value - .pop() - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Integer as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_integer_to_i64) - .ok_or(LdapProtoError::ControlAdDirsyncInteger)?; - - let cookie = value - .pop() - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()); - - Ok(LdapControl::AdDirsync { - flags, - max_bytes, - cookie, - }) - } - "1.2.840.113556.1.4.319" => { - let value_ber = value_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()) - .ok_or(LdapProtoError::ControlBer)?; - - let mut parser = Parser::new(); - let (_rem, value) = parser - .parse(&value_ber) - .map_err(|_| LdapProtoError::ControlBer)?; - - let mut value = value - .expect_constructed() - .ok_or(LdapProtoError::ControlBer)?; - - value.reverse(); - - let size = value - .pop() - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Integer as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_integer_to_i64) - .ok_or(LdapProtoError::ControlPagedInteger)?; - - let cookie = value - .pop() - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()) - .ok_or(LdapProtoError::ControlPagedCookie)?; - - Ok(LdapControl::SimplePagedResults { size, cookie }) - } - "2.16.840.1.113730.3.4.2" => { - // ManageDsaIT per https://www.rfc-editor.org/rfc/rfc3296 - - let criticality = criticality_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Boolean as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_bool_to_bool) - .unwrap_or(false); - - // Has no content. - - Ok(LdapControl::ManageDsaIT { criticality }) - } - "1.2.840.113556.1.4.474" => { - let value = value_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()) - .ok_or(LdapProtoError::ControlBer)?; - - let mut parser = Parser::new(); - let (_, tag) = parser - .parse(&value) - .map_err(|_| LdapProtoError::ControlBer)?; - - let mut tags = tag - .match_class(TagClass::Universal) - .and_then(|t| t.match_id(Types::Sequence as u64)) - .and_then(|t| t.expect_constructed()) - .ok_or(LdapProtoError::ControlBer)?; - - let enum_tag = tags.pop().ok_or(LdapProtoError::ControlBer)?; - - let value = enum_tag - .match_class(TagClass::Universal) - .and_then(|t| t.match_id(Types::Enumerated as u64)) - .and_then(|t| t.expect_primitive()) - .ok_or(LdapProtoError::ControlBer)?; - - let code = value.first().ok_or(LdapProtoError::ControlBer)?; - - Ok(LdapControl::ServerSortResult { - sort_result: ServerSortResult { - result_code: (*code as i64).try_into()?, - attribute_type: None, // TODO! - }, - }) - } - "1.3.6.1.4.1.42.2.27.8.5.1" => { - let criticality = criticality_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Boolean as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_bool_to_bool) - .unwrap_or(false); - - Ok(LdapControl::PasswordPolicyRequest { criticality }) - } - "1.2.840.113556.1.4.1338" => { - // SearchOptions control - let criticality = criticality_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Boolean as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_bool_to_bool) - .unwrap_or(false); - - let value_ber = value_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()) - .ok_or(LdapProtoError::ControlBer)?; - - let mut parser = Parser::new(); - let (_rem, value) = parser - .parse(&value_ber) - .map_err(|_| LdapProtoError::ControlBer)?; - - let mut value = value - .expect_constructed() - .ok_or(LdapProtoError::ControlBer)?; - - value.reverse(); - - let flags = value - .pop() - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Integer as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_integer_to_i64) - .ok_or(LdapProtoError::ControlSearchOptionsInteger)? - as i32; - - Ok(LdapControl::SearchOptions { criticality, flags }) - } - "1.2.840.113556.1.4.417" => { - // ShowDeleted control — no value - let criticality = criticality_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Boolean as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_bool_to_bool) - .unwrap_or(false); - - Ok(LdapControl::ShowDeleted { criticality }) - } - "1.2.840.113556.1.4.801" => { - // SdFlags control - let criticality = criticality_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Boolean as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_bool_to_bool) - .unwrap_or(false); - - let value_ber = value_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()) - .ok_or(LdapProtoError::ControlBer)?; - - let mut parser = Parser::new(); - let (_rem, value) = parser - .parse(&value_ber) - .map_err(|_| LdapProtoError::ControlBer)?; - - let mut value = value - .expect_constructed() - .ok_or(LdapProtoError::ControlBer)?; - - value.reverse(); - - let flags = value - .pop() - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Integer as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_integer_to_i64) - .ok_or(LdapProtoError::ControlSdFlagsInteger)? - as i32; - - Ok(LdapControl::SdFlags { criticality, flags }) - } - "1.2.840.113556.1.4.529" => { - // ExtendedDn control - let criticality = criticality_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Boolean as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_bool_to_bool) - .unwrap_or(false); - - let format = if let Some(value_ber) = value_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()) - { - let mut parser = Parser::new(); - let (_rem, value) = parser - .parse(&value_ber) - .map_err(|_| LdapProtoError::ControlBer)?; - - let mut value = value - .expect_constructed() - .ok_or(LdapProtoError::ControlBer)?; - - value.reverse(); - - value - .pop() - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Integer as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_integer_to_i64) - .ok_or(LdapProtoError::ControlExtendedDnInteger)? as i32 - } else { - // Absent value means hex format (0) - 0 - }; - - Ok(LdapControl::ExtendedDn { - criticality, - format, - }) - } - _ => { - warn!(%oid, "Unsupported control oid"); - - let criticality = criticality_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::Boolean as u64)) - .and_then(|t| t.expect_primitive()) - .and_then(ber_bool_to_bool) - .unwrap_or(false); - - let value = value_tag - .and_then(|t| t.match_class(TagClass::Universal)) - .and_then(|t| t.match_id(Types::OctetString as u64)) - .and_then(|t| t.expect_primitive()); - - Ok(LdapControl::Unknown { - oid, - criticality, - value, - }) - } - } - } -} - -impl From for Tag { - fn from(value: LdapControl) -> Tag { - let (oid, crit, inner_tag, raw_value) = match value { - LdapControl::SyncRequest { - criticality, - mode, - cookie, - reload_hint, - } => { - let inner: Vec<_> = vec![ - Some(Tag::Enumerated(Enumerated { - inner: mode as i64, - ..Default::default() - })), - cookie.map(|c| { - Tag::OctetString(OctetString { - inner: c, - ..Default::default() - }) - }), - if reload_hint { - Some(Tag::Boolean(Boolean { - inner: true, - ..Default::default() - })) - } else { - None - }, - ]; - - ( - "1.3.6.1.4.1.4203.1.9.1.1", - criticality, - Some(Tag::Sequence(Sequence { - inner: inner.into_iter().flatten().collect(), - ..Default::default() - })), - None, - ) - } - LdapControl::SyncState { - state, - entry_uuid, - cookie, - } => { - let inner: Vec<_> = vec![ - Some(Tag::Enumerated(Enumerated { - inner: state as i64, - ..Default::default() - })), - Some(Tag::OctetString(OctetString { - inner: entry_uuid.as_bytes().to_vec(), - ..Default::default() - })), - cookie.map(|c| { - Tag::OctetString(OctetString { - inner: c, - ..Default::default() - }) - }), - ]; - - ( - "1.3.6.1.4.1.4203.1.9.1.2", - false, - Some(Tag::Sequence(Sequence { - inner: inner.into_iter().flatten().collect(), - ..Default::default() - })), - None, - ) - } - LdapControl::SyncDone { - cookie, - refresh_deletes, - } => { - let inner: Vec<_> = vec![ - cookie.map(|c| { - Tag::OctetString(OctetString { - inner: c, - ..Default::default() - }) - }), - if refresh_deletes { - Some(Tag::Boolean(Boolean { - inner: true, - ..Default::default() - })) - } else { - None - }, - ]; - - ( - "1.3.6.1.4.1.4203.1.9.1.3", - false, - Some(Tag::Sequence(Sequence { - inner: inner.into_iter().flatten().collect(), - ..Default::default() - })), - None, - ) - } - LdapControl::AdDirsync { - flags, - max_bytes, - cookie, - } => { - let criticality = true; - let inner: Vec<_> = vec![ - Tag::Integer(Integer { - inner: flags, - ..Default::default() - }), - Tag::Integer(Integer { - inner: max_bytes, - ..Default::default() - }), - Tag::OctetString(OctetString { - inner: cookie.unwrap_or_default(), - ..Default::default() - }), - ]; - - ( - "1.2.840.113556.1.4.841", - criticality, - Some(Tag::Sequence(Sequence { - inner, - ..Default::default() - })), - None, - ) - } - LdapControl::SimplePagedResults { size, cookie } => { - let inner: Vec<_> = vec![ - Tag::Integer(Integer { - inner: size, - ..Default::default() - }), - Tag::OctetString(OctetString { - inner: cookie, - ..Default::default() - }), - ]; - - ( - "1.2.840.113556.1.4.319", - false, - Some(Tag::Sequence(Sequence { - inner, - ..Default::default() - })), - None, - ) - } - LdapControl::ManageDsaIT { criticality } => { - ("2.16.840.1.113730.3.4.2", criticality, None, None) - } - LdapControl::ServerSort { sort_requests } => { - let inner: Vec<_> = sort_requests - .into_iter() - .map(|sort_request| { - let mut inner = Vec::with_capacity(3); - inner.push(Tag::OctetString(OctetString { - inner: sort_request.attribute_name.into_bytes(), - ..Default::default() - })); - if let Some(ordering_rule) = sort_request.ordering_rule { - inner.push(Tag::OctetString(OctetString { - inner: ordering_rule.into_bytes(), - class: TagClass::Context, - id: 0, - })); - } - inner.push(Tag::Boolean(Boolean { - inner: sort_request.reverse_order, - class: TagClass::Context, - id: 1, - })); - Tag::Sequence(Sequence { - inner, - ..Default::default() - }) - }) - .collect(); - ( - "1.2.840.113556.1.4.473", - false, - Some(Tag::Sequence(Sequence { - inner, - ..Default::default() - })), - None, - ) - } - LdapControl::ServerSortResult { sort_result } => { - let inner = vec![ - Tag::Enumerated(Enumerated { - inner: sort_result.result_code as i64, - ..Default::default() - }), - Tag::OctetString(OctetString { - inner: sort_result.attribute_type.unwrap_or_default().into_bytes(), - ..Default::default() - }), - ]; - ( - "1.2.840.113556.1.4.474", - false, - Some(Tag::Sequence(Sequence { - inner, - ..Default::default() - })), - None, - ) - } - LdapControl::PasswordPolicyRequest { criticality } => { - ("1.3.6.1.4.1.42.2.27.8.5.1", criticality, None, None) - } - LdapControl::SearchOptions { criticality, flags } => { - let inner = vec![Tag::Integer(Integer { - inner: flags as i64, - ..Default::default() - })]; - ( - "1.2.840.113556.1.4.1338", - criticality, - Some(Tag::Sequence(Sequence { - inner, - ..Default::default() - })), - None, - ) - } - LdapControl::ShowDeleted { criticality } => { - ("1.2.840.113556.1.4.417", criticality, None, None) - } - LdapControl::SdFlags { criticality, flags } => { - let inner = vec![Tag::Integer(Integer { - inner: flags as i64, - ..Default::default() - })]; - ( - "1.2.840.113556.1.4.801", - criticality, - Some(Tag::Sequence(Sequence { - inner, - ..Default::default() - })), - None, - ) - } - LdapControl::ExtendedDn { - criticality, - format, - } => { - let inner = vec![Tag::Integer(Integer { - inner: format as i64, - ..Default::default() - })]; - ( - "1.2.840.113556.1.4.529", - criticality, - Some(Tag::Sequence(Sequence { - inner, - ..Default::default() - })), - None, - ) - } - LdapControl::Unknown { - ref oid, - criticality, - ref value, - } => (oid.as_str(), criticality, None, value.clone()), - }; - - let mut inner = Vec::with_capacity(3); - - inner.push(Tag::OctetString(OctetString { - inner: Vec::from(oid), - ..Default::default() - })); - if crit { - inner.push(Tag::Boolean(Boolean { - inner: true, - ..Default::default() - })); - } - - if let Some(inner_tag) = inner_tag { - let mut bytes = BytesMut::new(); - lber::write::encode_into(&mut bytes, inner_tag.into_structure()) - .expect("Failed to encode inner structure, this is a bug!"); - inner.push(Tag::OctetString(OctetString { - inner: bytes.to_vec(), - ..Default::default() - })); - } else if let Some(raw_value) = raw_value { - // Unknown control with raw value bytes — emit directly without re-encoding - inner.push(Tag::OctetString(OctetString { - inner: raw_value, - ..Default::default() - })); - } - - Tag::Sequence(Sequence { - inner, - ..Default::default() - }) - } -} +use std::fmt; + +use bytes::BytesMut; +use ldap3_lber::{ + Parser, + common::TagClass, + structure::StructureTag, + structures::{ASNTag, Boolean, Enumerated, Integer, OctetString, Sequence, Tag}, + universal::Types, + write as lber_write, +}; +use uuid::Uuid; + +use crate::{ + LdapResultCode, bytes_to_string, + error::LdapProtoError, + proto::{SyncRequestMode, SyncStateValue, ber_bool_to_bool, ber_integer_to_i64}, +}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +#[derive(Clone, PartialEq, Hash, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +pub enum LdapControl { + SyncRequest { + // Shouldn't this imply true? + criticality: bool, + mode: SyncRequestMode, + cookie: Option>, + reload_hint: bool, + }, + SyncState { + state: SyncStateValue, + entry_uuid: Uuid, + cookie: Option>, + }, + SyncDone { + cookie: Option>, + refresh_deletes: bool, + }, + AdDirsync { + flags: i64, + // Msdn and wireshark disagree on the name of this type. + max_bytes: i64, + cookie: Option>, + }, + // https://www.ietf.org/rfc/rfc2696.txt + SimplePagedResults { + size: i64, + cookie: Vec, + }, + ManageDsaIT { + criticality: bool, + }, + //1.2.840.113556.1.4.473 + ServerSort { + sort_requests: Vec, + }, + ServerSortResult { + sort_result: ServerSortResult, + }, + PasswordPolicyRequest { + criticality: bool, + }, + // 1.2.840.113556.1.4.1338 + SearchOptions { + criticality: bool, + flags: i32, + }, + // 1.2.840.113556.1.4.417 + ShowDeleted { + criticality: bool, + }, + // 1.2.840.113556.1.4.801 + SdFlags { + criticality: bool, + flags: i32, + }, + // 1.2.840.113556.1.4.529 + ExtendedDn { + criticality: bool, + format: i32, + }, + Unknown { + oid: String, + criticality: bool, + value: Option>, + }, +} + +/// Flag for `SearchOptions`: search across all naming contexts (phantom root). +pub const SEARCH_FLAG_PHANTOM_ROOT: i32 = 0x02; + +/// SD flags bitmask constants for `SdFlags` control. +pub const SD_FLAG_OWNER: i32 = 0x01; +pub const SD_FLAG_GROUP: i32 = 0x02; +pub const SD_FLAG_DACL: i32 = 0x04; +pub const SD_FLAG_SACL: i32 = 0x08; + +#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +pub struct ServerSortResult { + pub result_code: LdapResultCode, + pub attribute_type: Option, +} + +#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] +pub struct ServerSortRequet { + pub attribute_name: String, + pub ordering_rule: Option, + pub reverse_order: bool, +} + +impl fmt::Debug for LdapControl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + LdapControl::SyncRequest { + criticality, + mode, + cookie, + reload_hint, + } => { + let d_cookie = cookie.as_ref().map(|s| bytes_to_string!(s)); + f.debug_struct("LdapControl::SyncRequest") + .field("criticality", &criticality) + .field("mode", &mode) + .field("cookie", &d_cookie) + .field("reload_hint", &reload_hint) + .finish() + } + LdapControl::SyncState { + state, + entry_uuid, + cookie, + } => { + let d_cookie = cookie.as_ref().map(|s| bytes_to_string!(s)); + f.debug_struct("LdapControl::SyncState") + .field("state", &state) + .field("entry_uuid", &entry_uuid) + .field("cookie", &d_cookie) + .finish() + } + LdapControl::SyncDone { + cookie, + refresh_deletes, + } => { + let d_cookie = cookie.as_ref().map(|s| bytes_to_string!(s)); + f.debug_struct("LdapControl::SyncDone") + .field("refresh_deletes", &refresh_deletes) + .field("cookie", &d_cookie) + .finish() + } + LdapControl::AdDirsync { + flags, + max_bytes, + cookie, + } => { + let d_cookie = cookie.as_ref().map(|s| bytes_to_string!(s)); + f.debug_struct("LdapControl::AdDirsync") + .field("flags", &flags) + .field("max_bytes", &max_bytes) + .field("cookie", &d_cookie) + .finish() + } + LdapControl::SimplePagedResults { size, cookie } => { + let d_cookie = bytes_to_string!(cookie); + f.debug_struct("LdapControl::SimplePagedResults") + .field("size", &size) + .field("cookie", &d_cookie) + .finish() + } + LdapControl::ManageDsaIT { criticality } => f + .debug_struct("LdapControl::ManageDsaIT") + .field("criticality", &criticality) + .finish(), + + LdapControl::ServerSort { sort_requests } => f + .debug_struct("LdapControl::ServerSort") + .field("sort_requests", &sort_requests) + .finish(), + LdapControl::ServerSortResult { sort_result } => f + .debug_struct("LdapControl::ServerSortResult") + .field("sort_result", &sort_result) + .finish(), + LdapControl::PasswordPolicyRequest { criticality } => f + .debug_struct("LdapControl::PasswordPolicyRequest") + .field("criticality", &criticality) + .finish(), + LdapControl::SearchOptions { criticality, flags } => f + .debug_struct("LdapControl::SearchOptions") + .field("criticality", &criticality) + .field("flags", &flags) + .finish(), + LdapControl::ShowDeleted { criticality } => f + .debug_struct("LdapControl::ShowDeleted") + .field("criticality", &criticality) + .finish(), + LdapControl::SdFlags { criticality, flags } => f + .debug_struct("LdapControl::SdFlags") + .field("criticality", &criticality) + .field("flags", &flags) + .finish(), + LdapControl::ExtendedDn { + criticality, + format, + } => f + .debug_struct("LdapControl::ExtendedDn") + .field("criticality", &criticality) + .field("format", &format) + .finish(), + LdapControl::Unknown { + oid, + criticality, + value, + } => { + let d_value = value.as_ref().map(|v| bytes_to_string!(v)); + f.debug_struct("LdapControl::Unknown") + .field("oid", &oid) + .field("criticality", &criticality) + .field("value", &d_value) + .finish() + } + } + } +} + +impl TryFrom for LdapControl { + type Error = LdapProtoError; + + fn try_from(value: StructureTag) -> Result { + let mut seq = value + .match_id(Types::Sequence as u64) + .and_then(|t| t.expect_constructed()) + .ok_or(LdapProtoError::ControlBer)?; + + // We destructure in reverse order due to how vec in rust + // works. + let (oid_tag, criticality_tag, value_tag) = match seq.len() { + 1 => { + let v = None; + let c = None; + let o = seq.pop(); + (o, c, v) + } + 2 => { + let second = seq.pop(); + let o = seq.pop(); + // Distinguish criticality (Boolean, id=1) from value (OctetString, id=4) + let is_boolean = second + .as_ref() + .map(|t| t.class == TagClass::Universal && t.id == Types::Boolean as u64) + .unwrap_or(false); + if is_boolean { + (o, second, None) + } else { + (o, None, second) + } + } + 3 => { + let v = seq.pop(); + let c = seq.pop(); + let o = seq.pop(); + (o, c, v) + } + _ => return Err(LdapProtoError::ControlSeqLen), + }; + + trace!(?oid_tag, ?criticality_tag, ?value_tag); + + // We need to know what the oid is first. + let oid = oid_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(|bv| String::from_utf8(bv).ok()) + .ok_or(LdapProtoError::ControlBer)?; + + match oid.as_str() { + "1.3.6.1.4.1.4203.1.9.1.1" => { + // parse as sync req + let criticality = criticality_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Boolean as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_bool_to_bool) + .unwrap_or(false); + + let value_ber = value_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()) + .ok_or(LdapProtoError::ControlBer)?; + + let mut parser = Parser::default(); + let (_rem, value) = parser + .parse(&value_ber) + .map_err(|_| LdapProtoError::ControlBer)?; + + let mut value = value + .expect_constructed() + .ok_or(LdapProtoError::ControlBer)?; + + value.reverse(); + + let mode = value + .pop() + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Enumerated as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_integer_to_i64) + .and_then(|v| match v { + 1 => Some(SyncRequestMode::RefreshOnly), + 3 => Some(SyncRequestMode::RefreshAndPersist), + _ => None, + }) + .ok_or(LdapProtoError::ControlSyncMode)?; + + let cookie = value + .pop() + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()); + + let reload_hint = value + .pop() + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Boolean as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_bool_to_bool) + .unwrap_or(false); + + Ok(LdapControl::SyncRequest { + criticality, + mode, + cookie, + reload_hint, + }) + } + "1.3.6.1.4.1.4203.1.9.1.2" => { + // parse as sync state control + + //criticality is ignored. + + let value_ber = value_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()) + .ok_or(LdapProtoError::ControlBer)?; + + let mut parser = Parser::default(); + let (_rem, value) = parser + .parse(&value_ber) + .map_err(|_| LdapProtoError::ControlBer)?; + + let mut value = value + .expect_constructed() + .ok_or(LdapProtoError::ControlBer)?; + + value.reverse(); + + let state = value + .pop() + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Enumerated as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_integer_to_i64) + .and_then(|v| match v { + 0 => Some(SyncStateValue::Present), + 1 => Some(SyncStateValue::Add), + 2 => Some(SyncStateValue::Modify), + 3 => Some(SyncStateValue::Delete), + _ => None, + }) + .ok_or(LdapProtoError::ControlSyncState)?; + + let entry_uuid = value + .pop() + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()) + .ok_or(LdapProtoError::ControlBer) + .and_then(|v| { + Uuid::from_slice(&v).map_err(|_| LdapProtoError::ControlSyncUuid) + })?; + + let cookie = value + .pop() + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()); + + Ok(LdapControl::SyncState { + state, + entry_uuid, + cookie, + }) + } + "1.3.6.1.4.1.4203.1.9.1.3" => { + // parse as sync done control + // criticality is ignored. + + let value_ber = value_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()) + .ok_or(LdapProtoError::ControlBer)?; + + let mut parser = Parser::default(); + let (_rem, value) = parser + .parse(&value_ber) + .map_err(|_| LdapProtoError::ControlBer)?; + + let mut value = value + .expect_constructed() + .ok_or(LdapProtoError::ControlBer)?; + + value.reverse(); + + let cookie = value + .pop() + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()); + + let refresh_deletes = value + .pop() + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Boolean as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_bool_to_bool) + .unwrap_or(false); + + Ok(LdapControl::SyncDone { + cookie, + refresh_deletes, + }) + } + "1.2.840.113556.1.4.841" => { + let value_ber = value_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()) + .ok_or(LdapProtoError::ControlBer)?; + + let mut parser = Parser::default(); + let (_rem, value) = parser + .parse(&value_ber) + .map_err(|_| LdapProtoError::ControlBer)?; + + let mut value = value + .expect_constructed() + .ok_or(LdapProtoError::ControlBer)?; + + value.reverse(); + + let flags = value + .pop() + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Integer as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_integer_to_i64) + .ok_or(LdapProtoError::ControlAdDirsyncInteger)?; + + let max_bytes = value + .pop() + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Integer as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_integer_to_i64) + .ok_or(LdapProtoError::ControlAdDirsyncInteger)?; + + let cookie = value + .pop() + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()); + + Ok(LdapControl::AdDirsync { + flags, + max_bytes, + cookie, + }) + } + "1.2.840.113556.1.4.319" => { + let value_ber = value_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()) + .ok_or(LdapProtoError::ControlBer)?; + + let mut parser = Parser::default(); + let (_rem, value) = parser + .parse(&value_ber) + .map_err(|_| LdapProtoError::ControlBer)?; + + let mut value = value + .expect_constructed() + .ok_or(LdapProtoError::ControlBer)?; + + value.reverse(); + + let size = value + .pop() + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Integer as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_integer_to_i64) + .ok_or(LdapProtoError::ControlPagedInteger)?; + + let cookie = value + .pop() + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()) + .ok_or(LdapProtoError::ControlPagedCookie)?; + + Ok(LdapControl::SimplePagedResults { size, cookie }) + } + "2.16.840.1.113730.3.4.2" => { + // ManageDsaIT per https://www.rfc-editor.org/rfc/rfc3296 + + let criticality = criticality_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Boolean as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_bool_to_bool) + .unwrap_or(false); + + // Has no content. + + Ok(LdapControl::ManageDsaIT { criticality }) + } + "1.2.840.113556.1.4.474" => { + let value = value_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()) + .ok_or(LdapProtoError::ControlBer)?; + + let mut parser = Parser::default(); + let (_, tag) = parser + .parse(&value) + .map_err(|_| LdapProtoError::ControlBer)?; + + let mut tags = tag + .match_class(TagClass::Universal) + .and_then(|t| t.match_id(Types::Sequence as u64)) + .and_then(|t| t.expect_constructed()) + .ok_or(LdapProtoError::ControlBer)?; + + let enum_tag = tags.pop().ok_or(LdapProtoError::ControlBer)?; + + let value = enum_tag + .match_class(TagClass::Universal) + .and_then(|t| t.match_id(Types::Enumerated as u64)) + .and_then(|t| t.expect_primitive()) + .ok_or(LdapProtoError::ControlBer)?; + + let code = value.first().ok_or(LdapProtoError::ControlBer)?; + + Ok(LdapControl::ServerSortResult { + sort_result: ServerSortResult { + result_code: (*code as i64).try_into()?, + attribute_type: None, // TODO! + }, + }) + } + "1.3.6.1.4.1.42.2.27.8.5.1" => { + let criticality = criticality_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Boolean as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_bool_to_bool) + .unwrap_or(false); + + Ok(LdapControl::PasswordPolicyRequest { criticality }) + } + "1.2.840.113556.1.4.1338" => { + // SearchOptions control + let criticality = criticality_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Boolean as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_bool_to_bool) + .unwrap_or(false); + + let value_ber = value_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()) + .ok_or(LdapProtoError::ControlBer)?; + + let mut parser = Parser::default(); + let (_rem, value) = parser + .parse(&value_ber) + .map_err(|_| LdapProtoError::ControlBer)?; + + let mut value = value + .expect_constructed() + .ok_or(LdapProtoError::ControlBer)?; + + value.reverse(); + + let flags = value + .pop() + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Integer as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_integer_to_i64) + .ok_or(LdapProtoError::ControlSearchOptionsInteger)? + as i32; + + Ok(LdapControl::SearchOptions { criticality, flags }) + } + "1.2.840.113556.1.4.417" => { + // ShowDeleted control — no value + let criticality = criticality_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Boolean as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_bool_to_bool) + .unwrap_or(false); + + Ok(LdapControl::ShowDeleted { criticality }) + } + "1.2.840.113556.1.4.801" => { + // SdFlags control + let criticality = criticality_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Boolean as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_bool_to_bool) + .unwrap_or(false); + + let value_ber = value_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()) + .ok_or(LdapProtoError::ControlBer)?; + + let mut parser = Parser::default(); + let (_rem, value) = parser + .parse(&value_ber) + .map_err(|_| LdapProtoError::ControlBer)?; + + let mut value = value + .expect_constructed() + .ok_or(LdapProtoError::ControlBer)?; + + value.reverse(); + + let flags = value + .pop() + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Integer as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_integer_to_i64) + .ok_or(LdapProtoError::ControlSdFlagsInteger)? + as i32; + + Ok(LdapControl::SdFlags { criticality, flags }) + } + "1.2.840.113556.1.4.529" => { + // ExtendedDn control + let criticality = criticality_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Boolean as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_bool_to_bool) + .unwrap_or(false); + + let format = if let Some(value_ber) = value_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()) + { + let mut parser = Parser::default(); + let (_rem, value) = parser + .parse(&value_ber) + .map_err(|_| LdapProtoError::ControlBer)?; + + let mut value = value + .expect_constructed() + .ok_or(LdapProtoError::ControlBer)?; + + value.reverse(); + + value + .pop() + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Integer as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_integer_to_i64) + .ok_or(LdapProtoError::ControlExtendedDnInteger)? as i32 + } else { + // Absent value means hex format (0) + 0 + }; + + Ok(LdapControl::ExtendedDn { + criticality, + format, + }) + } + _ => { + warn!(%oid, "Unsupported control oid"); + + let criticality = criticality_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::Boolean as u64)) + .and_then(|t| t.expect_primitive()) + .and_then(ber_bool_to_bool) + .unwrap_or(false); + + let value = value_tag + .and_then(|t| t.match_class(TagClass::Universal)) + .and_then(|t| t.match_id(Types::OctetString as u64)) + .and_then(|t| t.expect_primitive()); + + Ok(LdapControl::Unknown { + oid, + criticality, + value, + }) + } + } + } +} + +impl From for Tag { + fn from(value: LdapControl) -> Tag { + let (oid, crit, inner_tag, raw_value) = match value { + LdapControl::SyncRequest { + criticality, + mode, + cookie, + reload_hint, + } => { + let inner: Vec<_> = vec![ + Some(Tag::Enumerated(Enumerated { + inner: mode as i64, + ..Default::default() + })), + cookie.map(|c| { + Tag::OctetString(OctetString { + inner: c, + ..Default::default() + }) + }), + if reload_hint { + Some(Tag::Boolean(Boolean { + inner: true, + ..Default::default() + })) + } else { + None + }, + ]; + + ( + "1.3.6.1.4.1.4203.1.9.1.1", + criticality, + Some(Tag::Sequence(Sequence { + inner: inner.into_iter().flatten().collect(), + ..Default::default() + })), + None, + ) + } + LdapControl::SyncState { + state, + entry_uuid, + cookie, + } => { + let inner: Vec<_> = vec![ + Some(Tag::Enumerated(Enumerated { + inner: state as i64, + ..Default::default() + })), + Some(Tag::OctetString(OctetString { + inner: entry_uuid.as_bytes().to_vec(), + ..Default::default() + })), + cookie.map(|c| { + Tag::OctetString(OctetString { + inner: c, + ..Default::default() + }) + }), + ]; + + ( + "1.3.6.1.4.1.4203.1.9.1.2", + false, + Some(Tag::Sequence(Sequence { + inner: inner.into_iter().flatten().collect(), + ..Default::default() + })), + None, + ) + } + LdapControl::SyncDone { + cookie, + refresh_deletes, + } => { + let inner: Vec<_> = vec![ + cookie.map(|c| { + Tag::OctetString(OctetString { + inner: c, + ..Default::default() + }) + }), + if refresh_deletes { + Some(Tag::Boolean(Boolean { + inner: true, + ..Default::default() + })) + } else { + None + }, + ]; + + ( + "1.3.6.1.4.1.4203.1.9.1.3", + false, + Some(Tag::Sequence(Sequence { + inner: inner.into_iter().flatten().collect(), + ..Default::default() + })), + None, + ) + } + LdapControl::AdDirsync { + flags, + max_bytes, + cookie, + } => { + let criticality = true; + let inner: Vec<_> = vec![ + Tag::Integer(Integer { + inner: flags, + ..Default::default() + }), + Tag::Integer(Integer { + inner: max_bytes, + ..Default::default() + }), + Tag::OctetString(OctetString { + inner: cookie.unwrap_or_default(), + ..Default::default() + }), + ]; + + ( + "1.2.840.113556.1.4.841", + criticality, + Some(Tag::Sequence(Sequence { + inner, + ..Default::default() + })), + None, + ) + } + LdapControl::SimplePagedResults { size, cookie } => { + let inner: Vec<_> = vec![ + Tag::Integer(Integer { + inner: size, + ..Default::default() + }), + Tag::OctetString(OctetString { + inner: cookie, + ..Default::default() + }), + ]; + + ( + "1.2.840.113556.1.4.319", + false, + Some(Tag::Sequence(Sequence { + inner, + ..Default::default() + })), + None, + ) + } + LdapControl::ManageDsaIT { criticality } => { + ("2.16.840.1.113730.3.4.2", criticality, None, None) + } + LdapControl::ServerSort { sort_requests } => { + let inner: Vec<_> = sort_requests + .into_iter() + .map(|sort_request| { + let mut inner = Vec::with_capacity(3); + inner.push(Tag::OctetString(OctetString { + inner: sort_request.attribute_name.into_bytes(), + ..Default::default() + })); + if let Some(ordering_rule) = sort_request.ordering_rule { + inner.push(Tag::OctetString(OctetString { + inner: ordering_rule.into_bytes(), + class: TagClass::Context, + id: 0, + })); + } + inner.push(Tag::Boolean(Boolean { + inner: sort_request.reverse_order, + class: TagClass::Context, + id: 1, + })); + Tag::Sequence(Sequence { + inner, + ..Default::default() + }) + }) + .collect(); + ( + "1.2.840.113556.1.4.473", + false, + Some(Tag::Sequence(Sequence { + inner, + ..Default::default() + })), + None, + ) + } + LdapControl::ServerSortResult { sort_result } => { + let inner = vec![ + Tag::Enumerated(Enumerated { + inner: sort_result.result_code as i64, + ..Default::default() + }), + Tag::OctetString(OctetString { + inner: sort_result.attribute_type.unwrap_or_default().into_bytes(), + ..Default::default() + }), + ]; + ( + "1.2.840.113556.1.4.474", + false, + Some(Tag::Sequence(Sequence { + inner, + ..Default::default() + })), + None, + ) + } + LdapControl::PasswordPolicyRequest { criticality } => { + ("1.3.6.1.4.1.42.2.27.8.5.1", criticality, None, None) + } + LdapControl::SearchOptions { criticality, flags } => { + let inner = vec![Tag::Integer(Integer { + inner: flags as i64, + ..Default::default() + })]; + ( + "1.2.840.113556.1.4.1338", + criticality, + Some(Tag::Sequence(Sequence { + inner, + ..Default::default() + })), + None, + ) + } + LdapControl::ShowDeleted { criticality } => { + ("1.2.840.113556.1.4.417", criticality, None, None) + } + LdapControl::SdFlags { criticality, flags } => { + let inner = vec![Tag::Integer(Integer { + inner: flags as i64, + ..Default::default() + })]; + ( + "1.2.840.113556.1.4.801", + criticality, + Some(Tag::Sequence(Sequence { + inner, + ..Default::default() + })), + None, + ) + } + LdapControl::ExtendedDn { + criticality, + format, + } => { + let inner = vec![Tag::Integer(Integer { + inner: format as i64, + ..Default::default() + })]; + ( + "1.2.840.113556.1.4.529", + criticality, + Some(Tag::Sequence(Sequence { + inner, + ..Default::default() + })), + None, + ) + } + LdapControl::Unknown { + ref oid, + criticality, + ref value, + } => (oid.as_str(), criticality, None, value.clone()), + }; + + let mut inner = Vec::with_capacity(3); + + inner.push(Tag::OctetString(OctetString { + inner: Vec::from(oid), + ..Default::default() + })); + if crit { + inner.push(Tag::Boolean(Boolean { + inner: true, + ..Default::default() + })); + } + + if let Some(inner_tag) = inner_tag { + let mut bytes = BytesMut::new(); + lber_write::encode_into(&mut bytes, inner_tag.into_structure()) + .expect("Failed to encode inner structure, this is a bug!"); + inner.push(Tag::OctetString(OctetString { + inner: bytes.to_vec(), + ..Default::default() + })); + } else if let Some(raw_value) = raw_value { + // Unknown control with raw value bytes — emit directly without re-encoding + inner.push(Tag::OctetString(OctetString { + inner: raw_value, + ..Default::default() + })); + } + + Tag::Sequence(Sequence { + inner, + ..Default::default() + }) + } +} diff --git a/proto/src/filter.rs b/proto/src/filter.rs index d4b6dca..f5759e3 100644 --- a/proto/src/filter.rs +++ b/proto/src/filter.rs @@ -1,11 +1,11 @@ //! LDAP Filter Parser use crate::{ - proto::{LdapMatchingRuleAssertion, LdapSubstringFilter}, LdapFilter, + proto::{LdapMatchingRuleAssertion, LdapSubstringFilter}, }; -const LDAP_FILTER_MAX_DEPTH: usize = 128; +const LDAP_FILTER_MAX_DEPTH: usize = 64; peg::parser! { pub(crate) grammar ldapfilter() for str { @@ -129,11 +129,11 @@ pub fn deserialise_ldap_filter<'de, D>(des: D) -> Result where D: serde::Deserializer<'de>, { - use serde::{de::Error, Deserialize}; + use serde::{Deserialize, de::Error}; - let raw = <&str>::deserialize(des)?; + let raw = ::deserialize(des)?; - ldapfilter::parse(raw, LDAP_FILTER_MAX_DEPTH) + ldapfilter::parse(&raw, LDAP_FILTER_MAX_DEPTH) .map_err(|err| D::Error::custom(format!("{:?}", err))) } diff --git a/proto/src/lib.rs b/proto/src/lib.rs index 74d1258..396c889 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -22,9 +22,11 @@ pub mod proto; pub mod simple; use bytes::{Buf, BytesMut}; -use lber::parse::Parser; -use lber::structure::StructureTag; -use lber::write as lber_write; +use ldap3_lber::{ + parse::{DEFAULT_MAX_BER_DEPTH, Parser}, + structure::StructureTag, + write as lber_write, +}; use std::io; use tokio_util::codec::{Decoder, Encoder}; use tracing::{error, trace}; @@ -38,20 +40,27 @@ pub const DEFAULT_MAX_BER_SIZE: usize = 32 * KILOBYTES; pub struct LdapCodec { /// Default is [DEFAULT_MAX_BER_SIZE] max_ber_size: usize, + /// Default is [DEFAULT_MAX_BER_DEPTH] + max_ber_depth: usize, } impl Default for LdapCodec { fn default() -> Self { LdapCodec { max_ber_size: DEFAULT_MAX_BER_SIZE, + max_ber_depth: DEFAULT_MAX_BER_DEPTH, } } } impl LdapCodec { - pub fn new(max_ber_size: Option) -> Self { + pub fn new(max_ber_size: Option, max_ber_depth: Option) -> Self { let max_ber_size = max_ber_size.unwrap_or(DEFAULT_MAX_BER_SIZE); - LdapCodec { max_ber_size } + let max_ber_depth = max_ber_depth.unwrap_or(DEFAULT_MAX_BER_DEPTH); + LdapCodec { + max_ber_size, + max_ber_depth, + } } } @@ -61,7 +70,7 @@ impl Decoder for LdapCodec { fn decode(&mut self, buf: &mut BytesMut) -> Result, Self::Error> { // How many bytes to consume? - let mut parser = Parser::new(); + let mut parser = Parser::new(self.max_ber_depth); let (rem, msg) = match parser.parse(buf) { Ok(r) => r, // Need more data @@ -108,15 +117,18 @@ impl Encoder for LdapCodec { #[cfg(test)] mod tests { + use crate::LdapCodec; use crate::control::LdapControl; use crate::error::LdapProtoError; use crate::parse_ldap_filter_str; use crate::proto::*; - use crate::LdapCodec; use bytes::BytesMut; - use lber::common::TagClass; - use lber::structure::StructureTag; - use lber::structures::{ASNTag, Tag}; + use ldap3_lber::{ + common::TagClass, + parse::Parser, + structure::StructureTag, + structures::{ASNTag, Tag}, + }; use std::convert::TryInto; use tokio_util::codec::{Decoder, Encoder}; @@ -401,7 +413,7 @@ mod tests { fn test_modify_from_raw() { use std::convert::TryFrom; - let mut parser = lber::parse::Parser::new(); + let mut parser = Parser::default(); let (_rem, msg) = parser .parse(&[ 48, 69, 2, 1, 2, 102, 64, 4, 39, 117, 105, 100, 61, 98, 106, 101, 110, 115, 101, @@ -409,7 +421,7 @@ mod tests { 109, 112, 108, 101, 44, 100, 99, 61, 99, 111, 109, 48, 21, 48, 19, 10, 1, 2, 48, 14, 4, 2, 115, 110, 49, 8, 4, 6, 77, 111, 114, 114, 105, 115, ]) - .unwrap(); + .expect("failed to parse input"); let op = LdapMsg::try_from(msg.clone()).expect("failed to decode"); eprintln!("{:?}", op); @@ -459,13 +471,13 @@ mod tests { let _ = tracing_subscriber::fmt::try_init(); - let mut parser = lber::parse::Parser::new(); + let mut parser = Parser::default(); let (_rem, msg) = parser .parse(&[ 48, 35, 2, 1, 2, 101, 30, 10, 2, 16, 0, 4, 0, 4, 22, 73, 110, 118, 97, 108, 105, 100, 32, 115, 101, 115, 115, 105, 111, 110, 32, 99, 111, 111, 107, 105, 101, ]) - .unwrap(); + .expect("failed to parse input"); let op = LdapMsg::try_from(msg.clone()).expect("failed to decode"); eprintln!("{:?}", op); @@ -480,7 +492,9 @@ mod tests { }; let ler: LdapExtendedRequest = mrq.clone().into(); - let mrq_dec: LdapPasswordModifyRequest = (&ler).try_into().unwrap(); + let mrq_dec: LdapPasswordModifyRequest = (&ler) + .try_into() + .expect("failed to decode LdapPasswordModifyRequest"); assert!(mrq == mrq_dec); let mrs = LdapPasswordModifyResponse { @@ -494,7 +508,9 @@ mod tests { }; let ler: LdapExtendedResponse = mrs.clone().into(); - let mrs_dec: LdapPasswordModifyResponse = (&ler).try_into().unwrap(); + let mrs_dec: LdapPasswordModifyResponse = (&ler) + .try_into() + .expect("failed to decode LdapPasswordModifyResponse"); assert!(mrs == mrs_dec); } @@ -563,7 +579,7 @@ mod tests { #[test] fn test_message_partial() { - let mut parser = lber::parse::Parser::new(); + let mut parser = Parser::default(); let parse_result = parser.parse(&[ 48, 69, 2, 1, 2, 102, 64, 4, 39, 117, 105, 100, 61, 98, 106, 101, 110, 115, 101, 110, @@ -578,18 +594,20 @@ mod tests { #[test] fn test_parse_wild_card() { - let filter = parse_ldap_filter_str("(cn=Info*Tech*Test)").unwrap(); - let tag: Tag = filter.try_into().expect("filters cannot convert into tags"); + let filter = + parse_ldap_filter_str("(cn=Info*Tech*Test)").expect("failed to parse substring filter"); + let tag: Tag = filter.into(); if let Tag::Sequence(mut sequence) = tag { assert_eq!(sequence.id, 4); assert_eq!(sequence.class, TagClass::Context); - let mut substring_filter_tags = match sequence.inner.pop().unwrap() { - Tag::Sequence(sequence) => sequence, - _ => panic!("substring_filter_tags should be sequence"), - } - .inner; - let cn = sequence.inner.pop().unwrap(); + let mut substring_filter_tags = + match sequence.inner.pop().expect("expecting substring filter") { + Tag::Sequence(sequence) => sequence, + _ => panic!("substring_filter_tags should be sequence"), + } + .inner; + let cn = sequence.inner.pop().expect("expecting cn"); match cn { Tag::OctetString(string) => { assert_eq!(string.id, 4); @@ -676,7 +694,7 @@ mod tests { #[test] pub fn test_present_filter() { - let filter = parse_ldap_filter_str("(cn=*)").unwrap(); + let filter = parse_ldap_filter_str("(cn=*)").expect("failed to parse present filter"); if let LdapFilter::Present(p) = filter { assert_eq!(p, "cn") } else { @@ -789,7 +807,7 @@ mod tests { #[test] pub fn test_equality_filter() { - let filter = parse_ldap_filter_str("(cn=test)").unwrap(); + let filter = parse_ldap_filter_str("(cn=test)").expect("failed to parse equality filter"); if let LdapFilter::Equality(name, value) = filter { assert_eq!(name, "cn"); assert_eq!(value, "test") @@ -809,19 +827,24 @@ mod tests { let encoded: Tag = filter.into(); let encoded: StructureTag = encoded.into_structure(); - let result = ldap_filter_from_structure_tag(encoded.clone(), 0).unwrap_err(); + let result = ldap_filter_from_structure_tag(encoded.clone(), 0) + .expect_err("expected FilterRecursionDepth error"); assert!(matches!(result, LdapProtoError::FilterRecursionDepth)); - let result = ldap_filter_from_structure_tag(encoded.clone(), 1).unwrap_err(); + let result = ldap_filter_from_structure_tag(encoded.clone(), 1) + .expect_err("expected FilterRecursionDepth error"); assert!(matches!(result, LdapProtoError::FilterRecursionDepth)); - let result = ldap_filter_from_structure_tag(encoded.clone(), 2).unwrap_err(); + let result = ldap_filter_from_structure_tag(encoded.clone(), 2) + .expect_err("expected FilterRecursionDepth error"); assert!(matches!(result, LdapProtoError::FilterRecursionDepth)); - let result = ldap_filter_from_structure_tag(encoded.clone(), 3).unwrap_err(); + let result = ldap_filter_from_structure_tag(encoded.clone(), 3) + .expect_err("expected FilterRecursionDepth error"); assert!(matches!(result, LdapProtoError::FilterRecursionDepth)); // Is allowed! - let _result = ldap_filter_from_structure_tag(encoded, 4).unwrap(); + let _result = + ldap_filter_from_structure_tag(encoded, 4).expect("should succeed at depth 4"); } } diff --git a/proto/src/proto.rs b/proto/src/proto.rs index 9123c03..5bd30a1 100644 --- a/proto/src/proto.rs +++ b/proto/src/proto.rs @@ -1,13 +1,14 @@ -use lber::common::TagClass; -use lber::structure::{StructureTag, PL}; -use lber::structures::ASNTag; -use lber::structures::{ - Boolean, Enumerated, ExplicitTag, Integer, Null, OctetString, Sequence, Set, Tag, +use ldap3_lber::{ + common::TagClass, + parse::Parser, + structure::{PL, StructureTag}, + structures::ASNTag, + structures::{ + Boolean, Enumerated, ExplicitTag, Integer, Null, OctetString, Sequence, Set, Tag, + }, + universal::Types, + write as lber_write, }; -use lber::universal::Types; -use lber::write as lber_write; - -use lber::parse::Parser; use bytes::BytesMut; #[cfg(feature = "serde")] @@ -766,7 +767,7 @@ impl TryFrom<&LdapExtendedRequest> for LdapPasswordModifyRequest { return Err(LdapProtoError::PasswordModifyRequestEmpty); }; - let mut parser = Parser::new(); + let mut parser = Parser::default(); let (_rem, msg) = parser .parse(buf) .map_err(|_| LdapProtoError::PasswordModifyRequestBer)?; @@ -844,7 +845,7 @@ impl TryFrom<&LdapExtendedResponse> for LdapPasswordModifyResponse { return Err(LdapProtoError::PasswordModifyResponseEmpty); }; - let mut parser = Parser::new(); + let mut parser = Parser::default(); let (_rem, msg) = parser .parse(buf) .map_err(|_| LdapProtoError::PasswordModifyResponseBer)?; @@ -894,7 +895,7 @@ impl LdapMsg { } pub fn try_from_openldap_mem_dump(bytes: &[u8]) -> Result { - let mut parser = lber::parse::Parser::new(); + let mut parser = Parser::default(); let (r1_bytes, msgid_tag) = parser .parse(bytes) .map_err(|_| LdapProtoError::OlMemDumpBer)?; @@ -1672,7 +1673,7 @@ pub(crate) fn ldap_filter_from_structure_tag( .and_then(|bv| { let mut filter = LdapSubstringFilter::default(); for ( - i, + tag_index, StructureTag { class: _, id, @@ -1681,26 +1682,18 @@ pub(crate) fn ldap_filter_from_structure_tag( ) in bv.iter().enumerate() { match (id, payload) { - (0, PL::P(s)) => { - if i == 0 { - // If 'initial' is present, it SHALL - // be the first element of 'substrings'. - filter.initial = Some(String::from_utf8(s.clone()).ok()?); - } else { - return None; - } + (0, PL::P(s)) if tag_index == 0 => { + // If 'initial' is present, it SHALL + // be the first element of 'substrings'. + filter.initial = Some(String::from_utf8(s.clone()).ok()?); } (1, PL::P(s)) => { filter.any.push(String::from_utf8(s.clone()).ok()?); } - (2, PL::P(s)) => { - if i == bv.len() - 1 { - // If 'final' is present, it - // SHALL be the last element of 'substrings'. - filter.final_ = Some(String::from_utf8(s.clone()).ok()?); - } else { - return None; - } + (2, PL::P(s)) if tag_index == bv.len() - 1 => { + // If 'final' is present, it + // SHALL be the last element of 'substrings'. + filter.final_ = Some(String::from_utf8(s.clone()).ok()?); } _ => return None, } @@ -2585,7 +2578,7 @@ impl TryFrom> for LdapIntermediateResponse { match (name.as_deref(), value.as_ref()) { (Some("1.3.6.1.4.1.4203.1.9.1.4"), Some(buf)) => { // It's a sync info done. Start to process the value. - let mut parser = Parser::new(); + let mut parser = Parser::default(); let (_rem, msg) = parser .parse(buf) .map_err(|_| LdapProtoError::IntermediateResponseBer)?; diff --git a/proto/src/simple.rs b/proto/src/simple.rs index 886e2a6..b89182f 100644 --- a/proto/src/simple.rs +++ b/proto/src/simple.rs @@ -106,7 +106,7 @@ impl TryFrom for ServerOps { } impl DisconnectionNotice { - pub fn gen(code: LdapResultCode, msg: &str) -> LdapMsg { + pub fn gen_response(code: LdapResultCode, msg: &str) -> LdapMsg { // name 1.3.6.1.4.1.1466.20036 // value == "" LdapMsg {