Skip to main content

ldap_parser/
ldap.rs

1//! Definitions for LDAP types
2
3use crate::error::Result;
4use crate::filter::*;
5use asn1_rs::{FromBer, ToStatic};
6use rusticata_macros::newtype_enum;
7use std::borrow::Cow;
8
9/// Hard limit for maximum recursion depth when parsing LDAP `Filter`
10pub const MAX_FILTER_DEPTH: usize = 32;
11
12#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, ToStatic)]
13pub struct ProtocolOpTag(pub u32);
14
15newtype_enum! {
16impl display ProtocolOpTag {
17    BindRequest = 0,
18    BindResponse = 1,
19    UnbindRequest = 2,
20    SearchRequest = 3,
21    SearchResultEntry = 4,
22    SearchResultDone = 5,
23    ModifyRequest = 6,
24    ModifyResponse = 7,
25    AddRequest = 8,
26    AddResponse = 9,
27    DelRequest = 10,
28    DelResponse = 11,
29    ModDnRequest = 12,
30    ModDnResponse = 13,
31    CompareRequest = 14,
32    CompareResponse = 15,
33    AbandonRequest = 16,
34    SearchResultReference = 19,
35    ExtendedRequest = 23,
36    ExtendedResponse = 24,
37    IntermediateResponse = 25,
38}
39}
40
41#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, ToStatic)]
42pub struct ResultCode(pub u32);
43
44newtype_enum! {
45impl debug ResultCode {
46    Success = 0,
47    OperationsError = 1,
48    ProtocolError = 2,
49    TimeLimitExceeded = 3,
50    SizeLimitExceeded = 4,
51    CompareFalse = 5,
52    CompareTrue = 6,
53    AuthMethodNotSupported = 7,
54    StrongerAuthRequired = 8,
55    // -- 9 reserved --
56    Referral = 10,
57    AdminLimitExceeded = 11,
58    UnavailableCriticalExtension = 12,
59    ConfidentialityRequired = 13,
60    SaslBindInProgress = 14,
61    NoSuchAttribute = 16,
62    UndefinedAttributeType = 17,
63    InappropriateMatching = 18,
64    ConstraintViolation = 19,
65    AttributeOrValueExists = 20,
66    InvalidAttributeSyntax = 21,
67    // -- 22-31 unused --
68    NoSuchObject = 32,
69    AliasProblem = 33,
70    InvalidDNSyntax = 34,
71    // -- 35 reserved for undefined isLeaf --
72    AliasDereferencingProblem = 36,
73    // -- 37-47 unused --
74    InappropriateAuthentication = 48,
75    InvalidCredentials = 49,
76    InsufficientAccessRights = 50,
77    Busy = 51,
78    Unavailable = 52,
79    UnwillingToPerform = 53,
80    LoopDetect = 54,
81    // -- 55-63 unused --
82    NamingViolation = 64,
83    ObjectClassViolation = 65,
84    NotAllowedOnNonLeaf = 66,
85    NotAllowedOnRDN = 67,
86    EntryAlreadyExists = 68,
87    ObjectClassModsProhibited = 69,
88    // -- 70 reserved for CLDAP --
89    AffectsMultipleDSAs = 71,
90    // -- 72-79 unused --
91    Other = 80,
92}
93}
94
95#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, ToStatic)]
96pub struct MessageID(pub u32);
97
98#[derive(PartialEq, Eq, Clone, Copy, ToStatic)]
99pub struct SearchScope(pub u32);
100
101newtype_enum! {
102impl debug SearchScope {
103    BaseObject = 0,
104    SingleLevel = 1,
105    WholeSubtree = 2,
106}
107}
108
109#[derive(PartialEq, Eq, Clone, Copy, ToStatic)]
110pub struct DerefAliases(pub u32);
111
112newtype_enum! {
113impl debug DerefAliases {
114    NeverDerefAliases = 0,
115    DerefInSearching = 1,
116    DerefFindingBaseObj = 2,
117    DerefAlways = 3,
118}
119}
120
121#[derive(PartialEq, Eq, Clone, Copy, ToStatic)]
122pub struct Operation(pub u32);
123
124newtype_enum! {
125impl debug Operation {
126    Add = 0,
127    Delete = 1,
128    Replace = 2,
129}
130}
131
132#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
133pub struct LdapString<'a>(pub Cow<'a, str>);
134
135#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
136pub struct LdapDN<'a>(pub Cow<'a, str>);
137
138#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
139pub struct RelativeLdapDN<'a>(pub Cow<'a, str>);
140
141#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
142pub struct LdapOID<'a>(pub Cow<'a, str>);
143
144#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
145pub struct LdapResult<'a> {
146    pub result_code: ResultCode,
147    pub matched_dn: LdapDN<'a>,
148    pub diagnostic_message: LdapString<'a>,
149    // referral           [3] Referral OPTIONAL
150}
151
152#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
153pub struct BindRequest<'a> {
154    pub version: u8,
155    pub name: LdapDN<'a>,
156    pub authentication: AuthenticationChoice<'a>,
157}
158
159#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
160pub struct SaslCredentials<'a> {
161    pub mechanism: LdapString<'a>,
162    pub credentials: Option<Cow<'a, [u8]>>,
163}
164
165#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
166pub enum AuthenticationChoice<'a> {
167    Simple(Cow<'a, [u8]>),
168    Sasl(SaslCredentials<'a>),
169}
170
171#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
172pub struct BindResponse<'a> {
173    pub result: LdapResult<'a>,
174    pub server_sasl_creds: Option<Cow<'a, [u8]>>,
175}
176
177#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
178pub struct SearchRequest<'a> {
179    pub base_object: LdapDN<'a>,
180    pub scope: SearchScope,
181    pub deref_aliases: DerefAliases,
182    pub size_limit: u32,
183    pub time_limit: u32,
184    pub types_only: bool,
185    pub filter: Filter<'a>,
186    pub attributes: Vec<LdapString<'a>>,
187}
188
189#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
190pub struct SearchResultEntry<'a> {
191    pub object_name: LdapDN<'a>,
192    pub attributes: Vec<PartialAttribute<'a>>,
193}
194
195#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
196pub struct ModifyRequest<'a> {
197    pub object: LdapDN<'a>,
198    pub changes: Vec<Change<'a>>,
199}
200
201#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
202pub struct ModifyResponse<'a> {
203    pub result: LdapResult<'a>,
204}
205
206#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
207pub struct Change<'a> {
208    pub operation: Operation,
209    pub modification: PartialAttribute<'a>,
210}
211
212#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
213pub struct AddRequest<'a> {
214    pub entry: LdapDN<'a>,
215    pub attributes: Vec<Attribute<'a>>,
216}
217
218#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
219pub struct ModDnRequest<'a> {
220    pub entry: LdapDN<'a>,
221    pub newrdn: RelativeLdapDN<'a>,
222    pub deleteoldrdn: bool,
223    pub newsuperior: Option<LdapDN<'a>>,
224}
225
226#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
227pub struct CompareRequest<'a> {
228    pub entry: LdapDN<'a>,
229    pub ava: AttributeValueAssertion<'a>,
230}
231
232#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
233pub struct ExtendedRequest<'a> {
234    pub request_name: LdapOID<'a>,
235    pub request_value: Option<Cow<'a, [u8]>>,
236}
237
238#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
239pub struct ExtendedResponse<'a> {
240    pub result: LdapResult<'a>,
241    pub response_name: Option<LdapOID<'a>>,
242    pub response_value: Option<Cow<'a, [u8]>>,
243}
244
245#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
246pub struct IntermediateResponse<'a> {
247    pub response_name: Option<LdapOID<'a>>,
248    pub response_value: Option<Cow<'a, [u8]>>,
249}
250
251#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
252pub enum ProtocolOp<'a> {
253    BindRequest(BindRequest<'a>),
254    BindResponse(BindResponse<'a>),
255    UnbindRequest,
256    SearchRequest(SearchRequest<'a>),
257    SearchResultEntry(SearchResultEntry<'a>),
258    SearchResultDone(LdapResult<'a>),
259    SearchResultReference(Vec<LdapString<'a>>),
260    ModifyRequest(ModifyRequest<'a>),
261    ModifyResponse(ModifyResponse<'a>),
262    AddRequest(AddRequest<'a>),
263    AddResponse(LdapResult<'a>),
264    DelRequest(LdapDN<'a>),
265    DelResponse(LdapResult<'a>),
266    ModDnRequest(ModDnRequest<'a>),
267    ModDnResponse(LdapResult<'a>),
268    CompareRequest(CompareRequest<'a>),
269    CompareResponse(LdapResult<'a>),
270    //
271    AbandonRequest(MessageID),
272    ExtendedRequest(ExtendedRequest<'a>),
273    ExtendedResponse(ExtendedResponse<'a>),
274    IntermediateResponse(IntermediateResponse<'a>),
275}
276
277impl ProtocolOp<'_> {
278    /// Get tag number associated with the operation
279    pub fn tag(&self) -> ProtocolOpTag {
280        let op = match self {
281            ProtocolOp::BindRequest(_) => 0,
282            ProtocolOp::BindResponse(_) => 1,
283            ProtocolOp::UnbindRequest => 2,
284            ProtocolOp::SearchRequest(_) => 3,
285            ProtocolOp::SearchResultEntry(_) => 4,
286            ProtocolOp::SearchResultDone(_) => 5,
287            ProtocolOp::ModifyRequest(_) => 6,
288            ProtocolOp::ModifyResponse(_) => 7,
289            ProtocolOp::AddRequest(_) => 8,
290            ProtocolOp::AddResponse(_) => 9,
291            ProtocolOp::DelRequest(_) => 10,
292            ProtocolOp::DelResponse(_) => 11,
293            ProtocolOp::ModDnRequest(_) => 12,
294            ProtocolOp::ModDnResponse(_) => 13,
295            ProtocolOp::CompareRequest(_) => 14,
296            ProtocolOp::CompareResponse(_) => 15,
297            ProtocolOp::AbandonRequest(_) => 16,
298            ProtocolOp::SearchResultReference(_) => 19,
299            ProtocolOp::ExtendedRequest(_) => 23,
300            ProtocolOp::ExtendedResponse(_) => 24,
301            ProtocolOp::IntermediateResponse(_) => 25,
302        };
303        ProtocolOpTag(op)
304    }
305
306    /// Get the LDAP result, if present
307    pub fn result(&self) -> Option<&LdapResult<'_>> {
308        match self {
309            ProtocolOp::BindResponse(r) => Some(&r.result),
310            ProtocolOp::ModifyResponse(r) => Some(&r.result),
311            ProtocolOp::ExtendedResponse(r) => Some(&r.result),
312            ProtocolOp::SearchResultDone(ref r)
313            | ProtocolOp::AddResponse(ref r)
314            | ProtocolOp::DelResponse(ref r)
315            | ProtocolOp::ModDnResponse(ref r)
316            | ProtocolOp::CompareResponse(ref r) => Some(r),
317            _ => None,
318        }
319    }
320}
321
322#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
323pub struct Control<'a> {
324    pub control_type: LdapOID<'a>,
325    pub criticality: bool,
326    pub control_value: Option<Cow<'a, [u8]>>,
327}
328
329/// An LDAP Message according to RFC4511
330///
331// LDAPMessage ::= SEQUENCE {
332//      messageID       MessageID,
333//      protocolOp      CHOICE {
334//           bindRequest           BindRequest,
335//           bindResponse          BindResponse,
336//           unbindRequest         UnbindRequest,
337//           searchRequest         SearchRequest,
338//           searchResEntry        SearchResultEntry,
339//           searchResDone         SearchResultDone,
340//           searchResRef          SearchResultReference,
341//           modifyRequest         ModifyRequest,
342//           modifyResponse        ModifyResponse,
343//           addRequest            AddRequest,
344//           addResponse           AddResponse,
345//           delRequest            DelRequest,
346//           delResponse           DelResponse,
347//           modDNRequest          ModifyDNRequest,
348//           modDNResponse         ModifyDNResponse,
349//           compareRequest        CompareRequest,
350//           compareResponse       CompareResponse,
351//           abandonRequest        AbandonRequest,
352//           extendedReq           ExtendedRequest,
353//           extendedResp          ExtendedResponse,
354//           ...,
355//           intermediateResponse  IntermediateResponse },
356//      controls       [0] Controls OPTIONAL }
357/// Parse a single LDAP message and return a structure borrowing fields from the input buffer
358///
359/// ```rust
360/// use ldap_parser::FromBer;
361/// use ldap_parser::ldap::{LdapMessage, MessageID, ProtocolOp, ProtocolOpTag};
362///
363/// static DATA: &[u8] = include_bytes!("../assets/message-search-request-01.bin");
364///
365/// # fn main() {
366/// let res = LdapMessage::from_ber(DATA);
367/// match res {
368///     Ok((rem, msg)) => {
369///         assert!(rem.is_empty());
370///         //
371///         assert_eq!(msg.message_id, MessageID(4));
372///         assert_eq!(msg.protocol_op.tag(), ProtocolOpTag::SearchRequest);
373///         match msg.protocol_op {
374///             ProtocolOp::SearchRequest(req) => {
375///                 assert_eq!(req.base_object.0, "dc=rccad,dc=net");
376///             },
377///             _ => panic!("Unexpected message type"),
378///         }
379///     },
380///     _ => panic!("LDAP parsing failed: {:?}", res),
381/// }
382/// # }
383/// ```
384#[derive(Clone, Debug, Eq, PartialEq, ToStatic)]
385pub struct LdapMessage<'a> {
386    /// Message Identifier (32-bits unsigned integer)
387    ///
388    /// The messageID of a request MUST have a non-zero value different from the messageID of any
389    /// other request in progress in the same LDAP session.  The zero value is reserved for the
390    /// unsolicited notification message.
391    pub message_id: MessageID,
392    /// The LDAP operation from this LDAP message
393    pub protocol_op: ProtocolOp<'a>,
394    /// Message controls (optional)
395    ///
396    /// Controls provide a mechanism whereby the semantics and arguments of existing LDAP
397    /// operations may be extended.  One or more controls may be attached to a single LDAP message.
398    /// A control only affects the semantics of the message it is attached to.
399    pub controls: Option<Vec<Control<'a>>>,
400}
401
402impl<'a> LdapMessage<'a> {
403    /// Parse a single LDAP message and return a structure borrowing fields from the input buffer
404    #[deprecated(
405        since = "0.3.0",
406        note = "Parsing functions are deprecated. Users should instead use the FromBer trait"
407    )]
408    #[inline]
409    pub fn parse(i: &'a [u8]) -> Result<'a, LdapMessage<'a>> {
410        Self::from_ber(i)
411    }
412}
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417
418    struct LDAPTransaction {
419        s: LdapString<'static>,
420    }
421
422    #[test]
423    fn test_transaction_lifetime() {
424        let s = "test";
425        let ldap_string = LdapString(s.into());
426        assert!(matches!(ldap_string.0, Cow::Borrowed(_)));
427
428        let ldap_string_owned = ldap_string.to_static();
429        assert!(matches!(ldap_string_owned.0, Cow::Owned(_)));
430
431        let tx = LDAPTransaction {
432            s: ldap_string_owned,
433        };
434        assert!(matches!(tx.s.0, Cow::Owned(_)));
435    }
436}