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