1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
//! Operation result structures and helpers.
//!
//! Most LDAP operations return an [`LdapResult`](struct.LdapResult.html). This module
//! contains its definition, as well as that of a number of wrapper structs and
//! helper methods, which adapt LDAP result and error handling to be a closer
//! match to Rust conventions.

use std::error::Error;
use std::fmt;
use std::io;
use std::result::Result as StdResult;

use crate::controls::Control;
use crate::exop::Exop;
use crate::protocol::{LdapOp, MaybeControls, ResultSender};
use crate::search::parse_refs;
use crate::search::ResultEntry;
use crate::RequestId;

use lber::common::TagClass;
use lber::parse::parse_uint;
use lber::structures::Tag;
use lber::universal::Types;
use lber::IResult;

use thiserror::Error;
use tokio::sync::{mpsc, oneshot};
use tokio::time;

/// Type alias for the standard `Result` with the fixed `LdapError` error part.
pub type Result<T> = std::result::Result<T, LdapError>;

/// Error variants recognized by the library.
#[derive(Debug, Error)]
pub enum LdapError {
    /// No path given for a `ldapi://` URL.
    #[error("empty Unix domain socket path")]
    EmptyUnixPath,

    /// A `ldapi://` URL contains a port spec, which it shouldn't.
    #[error("the port must be empty in the ldapi scheme")]
    PortInUnixPath,

    /// Encapsulated I/O error.
    #[error("I/O error: {source}")]
    Io {
        #[from]
        source: io::Error,
    },

    /// Error while sending an operation to the connection handler.
    #[error("op send error: {source}")]
    OpSend {
        #[from]
        source: mpsc::error::SendError<(RequestId, LdapOp, Tag, MaybeControls, ResultSender)>,
    },

    /// Error while receiving operation results from the connection handler.
    #[error("result recv error: {source}")]
    ResultRecv {
        #[from]
        source: oneshot::error::RecvError,
    },

    /// Error while sending an internal ID scrubbing request to the connection handler.
    #[error("id scrub send error: {source}")]
    IdScrubSend {
        #[from]
        source: mpsc::error::SendError<RequestId>,
    },

    /// Operation or connection timeout.
    #[error("timeout: {elapsed}")]
    Timeout {
        #[from]
        elapsed: time::error::Elapsed,
    },

    /// Error parsing the string representation of a search filter.
    #[error("filter parse error")]
    FilterParsing,

    /// Premature end of a search stream.
    #[error("premature end of search stream")]
    EndOfStream,

    /// URL parsing error.
    #[error("url parse error: {source}")]
    UrlParsing {
        #[from]
        source: url::ParseError,
    },

    /// Unknown LDAP URL scheme.
    #[error("unknown LDAP URL scheme: {0}")]
    UnknownScheme(String),

    #[cfg(feature = "tls-native")]
    /// Native TLS library error.
    #[error("native TLS error: {source}")]
    NativeTLS {
        #[from]
        source: native_tls::Error,
    },

    #[cfg(feature = "tls-rustls")]
    /// Rustls library error.
    #[error("rustls error: {source}")]
    Rustls {
        #[from]
        source: rustls::TLSError,
    },

    #[cfg(feature = "tls-rustls")]
    /// Rustls DNS name error.
    #[error("rustls DNS error: {source}")]
    DNSName {
        #[from]
        source: tokio_rustls::webpki::InvalidDNSNameError,
    },

    /// LDAP operation result with an error return code.
    #[error("LDAP operation result: {result}")]
    LdapResult {
        #[from]
        result: LdapResult,
    },

    /// No values provided for the Add operation.
    #[error("empty value set for Add")]
    AddNoValues,

    /// No values provided for the Add operation.
    #[error("adapter init error: {0}")]
    AdapterInit(String),
}

impl From<LdapError> for io::Error {
    fn from(le: LdapError) -> io::Error {
        match le {
            LdapError::Io { source, .. } => source,
            _ => io::Error::new(io::ErrorKind::Other, format!("{}", le)),
        }
    }
}

/// Common components of an LDAP operation result.
///
/// This structure faithfully replicates the components dictated by the standard,
/// and is distinctly C-like with its reliance on numeric codes for the indication
/// of outcome. It would be tempting to hide it behind an automatic `Result`-like
/// interface, but there are scenarios where this would preclude intentional
/// incorporation of error conditions into query design. Instead, the struct
/// implements helper methods, [`success()`](#method.success) and
/// [`non_error()`](#method.non_error), which may be used for ergonomic error
/// handling when simple condition checking suffices.
#[derive(Clone, Debug)]
pub struct LdapResult {
    /// Result code.
    ///
    /// Generally, the value of zero indicates successful completion, but there's
    /// a number of other non-error codes arising as a result of various operations.
    /// See [Section A.1 of RFC 4511](https://tools.ietf.org/html/rfc4511#appendix-A.1).
    pub rc: u32,
    /// Matched component DN, where applicable.
    pub matched: String,
    /// Additional diagnostic text.
    pub text: String,
    /// Referrals.
    ///
    /// Absence of referrals is represented by an empty vector.
    pub refs: Vec<String>,
    /// Response controls.
    ///
    /// Missing and empty controls are both represented by an empty vector.
    pub ctrls: Vec<Control>,
}

#[doc(hidden)]
impl From<Tag> for LdapResult {
    fn from(t: Tag) -> LdapResult {
        <LdapResultExt as From<Tag>>::from(t).0
    }
}

impl Error for LdapResult {}

impl fmt::Display for LdapResult {
    fn fmt(&self, f: &mut fmt::Formatter) -> StdResult<(), fmt::Error> {
        fn description(this: &LdapResult) -> &'static str {
            match this.rc {
                0 => "success",
                1 => "operationsError",
                2 => "protocolError",
                3 => "timeLimitExceeded",
                4 => "sizeLimitExceeded",
                5 => "compareFalse",
                6 => "compareTrue",
                7 => "authMethodNotSupported",
                8 => "strongerAuthRequired",
                10 => "referral",
                11 => "adminLimitExceeded",
                12 => "unavailableCriticalExtension",
                13 => "confidentialityRequired",
                14 => "saslBindInProgress",
                16 => "noSuchAttribute",
                17 => "undefinedAttributeType",
                18 => "inappropriateMatching",
                19 => "constraintViolation",
                20 => "attributeOrValueExists",
                21 => "invalidAttributeSyntax",
                32 => "noSuchObject",
                33 => "aliasProblem",
                34 => "invalidDNSyntax",
                36 => "aliasDereferencingProblem",
                48 => "inappropriateAuthentication",
                49 => "invalidCredentials",
                50 => "insufficientAccessRights",
                51 => "busy",
                52 => "unavailable",
                53 => "unwillingToPerform",
                54 => "loopDetect",
                64 => "namingViolation",
                65 => "objectClassViolation",
                66 => "notAllowedOnNonLeaf",
                67 => "notAllowedOnRDN",
                68 => "entryAlreadyExists",
                69 => "objectClassModsProhibited",
                71 => "affectsMultipleDSAs",
                80 => "other",
                88 => "abandoned",
                122 => "assertionFailed",
                _ => "unknown",
            }
        }

        write!(
            f,
            "rc={} ({}), dn: \"{}\", text: \"{}\"",
            self.rc,
            description(self),
            self.matched,
            self.text
        )
    }
}

impl LdapResult {
    /// If the result code is zero, return the instance itself wrapped
    /// in `Ok()`, otherwise wrap the instance in an `LdapError`.
    pub fn success(self) -> Result<Self> {
        if self.rc == 0 {
            Ok(self)
        } else {
            Err(LdapError::from(self))
        }
    }

    /// If the result code is 0 or 10 (referral), return the instance
    /// itself wrapped in `Ok()`, otherwise wrap the instance in an
    /// `LdapError`.
    pub fn non_error(self) -> Result<Self> {
        if self.rc == 0 || self.rc == 10 {
            Ok(self)
        } else {
            Err(LdapError::from(self))
        }
    }
}

#[derive(Clone, Debug)]
pub(crate) struct LdapResultExt(pub LdapResult, pub Exop);

impl From<Tag> for LdapResultExt {
    fn from(t: Tag) -> LdapResultExt {
        let t = match t {
            Tag::StructureTag(t) => t,
            Tag::Null(_) => {
                return LdapResultExt(
                    LdapResult {
                        rc: 0,
                        matched: String::from(""),
                        text: String::from(""),
                        refs: vec![],
                        ctrls: vec![],
                    },
                    Exop {
                        name: None,
                        val: None,
                    },
                )
            }
            _ => unimplemented!(),
        };
        let mut tags = t.expect_constructed().expect("result sequence").into_iter();
        let rc = match parse_uint(
            tags.next()
                .expect("element")
                .match_class(TagClass::Universal)
                .and_then(|t| t.match_id(Types::Enumerated as u64))
                .and_then(|t| t.expect_primitive())
                .expect("result code")
                .as_slice(),
        ) {
            IResult::Done(_, rc) => rc as u32,
            _ => panic!("failed to parse result code"),
        };
        let matched = String::from_utf8(
            tags.next()
                .expect("element")
                .expect_primitive()
                .expect("octet string"),
        )
        .expect("matched dn");
        let text = String::from_utf8(
            tags.next()
                .expect("element")
                .expect_primitive()
                .expect("octet string"),
        )
        .expect("diagnostic message");
        let mut refs = Vec::new();
        let mut exop_name = None;
        let mut exop_val = None;
        loop {
            match tags.next() {
                None => break,
                Some(comp) => match comp.id {
                    3 => {
                        refs.extend(parse_refs(comp));
                    }
                    10 => {
                        exop_name = Some(
                            String::from_utf8(comp.expect_primitive().expect("octet string"))
                                .expect("exop name"),
                        );
                    }
                    11 => {
                        exop_val = Some(comp.expect_primitive().expect("octet string"));
                    }
                    _ => (),
                },
            }
        }
        LdapResultExt(
            LdapResult {
                rc,
                matched,
                text,
                refs,
                ctrls: vec![],
            },
            Exop {
                name: exop_name,
                val: exop_val,
            },
        )
    }
}

/// Wrapper for results of a Search operation which returns all entries at once.
///
/// The wrapper exists so that methods [`success()`](#method.success) and
/// [`non_error()`](#method.non_error) can be called on an instance. Those methods
/// destructure the wrapper and return its components as elements of an anonymous
/// tuple.
#[derive(Clone, Debug)]
pub struct SearchResult(pub Vec<ResultEntry>, pub LdapResult);

impl SearchResult {
    /// If the result code is zero, return an anonymous tuple of component structs
    /// wrapped in `Ok()`, otherwise wrap the `LdapResult` part in an `LdapError`.
    pub fn success(self) -> Result<(Vec<ResultEntry>, LdapResult)> {
        if self.1.rc == 0 {
            Ok((self.0, self.1))
        } else {
            Err(LdapError::from(self.1))
        }
    }

    /// If the result code is 0 or 10 (referral), return an anonymous tuple of component
    /// structs wrapped in `Ok()`, otherwise wrap the `LdapResult` part in an `LdapError`.
    pub fn non_error(self) -> Result<(Vec<ResultEntry>, LdapResult)> {
        if self.1.rc == 0 || self.1.rc == 10 {
            Ok((self.0, self.1))
        } else {
            Err(LdapError::from(self.1))
        }
    }
}

/// Wrapper for the result of a Compare operation.
///
/// Compare uniquely has two non-zero return codes to indicate the outcome of a successful
/// comparison, while other return codes indicate errors, as usual (except 10 for referral).
/// The [`equal()`](#method.equal) method optimizes for the expected case of ignoring
/// referrals; [`non_error()`](#method.non_error) can be used when that's not possible.
#[derive(Clone, Debug)]
pub struct CompareResult(pub LdapResult);

impl CompareResult {
    /// If the result code is 5 (compareFalse) or 6 (compareTrue), return the corresponding
    /// boolean value wrapped in `Ok()`, otherwise wrap the `LdapResult` part in an `LdapError`.
    pub fn equal(self) -> Result<bool> {
        match self.0.rc {
            5 => Ok(false),
            6 => Ok(true),
            _ => Err(LdapError::from(self.0)),
        }
    }

    /// If the result code is 5 (compareFalse), 6 (compareTrue),  or 10 (referral), return
    /// the inner `LdapResult`, otherwise rewrap `LdapResult` in an `LdapError`.
    pub fn non_error(self) -> Result<LdapResult> {
        if self.0.rc == 5 || self.0.rc == 6 || self.0.rc == 10 {
            Ok(self.0)
        } else {
            Err(LdapError::from(self.0))
        }
    }
}

/// Wrapper for the result of an Extended operation.
///
/// Similarly to [`SearchResult`](struct.SearchResult.html), methods
/// [`success()`](#method.success) and [`non_error()`](#method.non_error) can be
/// called on an instance, and will destructure the wrapper into an anonymous
/// tuple of its components.
#[derive(Clone, Debug)]
pub struct ExopResult(pub Exop, pub LdapResult);

impl ExopResult {
    /// If the result code is zero, return an anonymous tuple of component structs
    /// wrapped in `Ok()`, otherwise wrap the `LdapResult` part in an `LdapError`.
    pub fn success(self) -> Result<(Exop, LdapResult)> {
        if self.1.rc == 0 {
            Ok((self.0, self.1))
        } else {
            Err(LdapError::from(self.1))
        }
    }

    /// If the result code is 0 or 10 (referral), return an anonymous tuple of component
    /// structs wrapped in `Ok()`, otherwise wrap the `LdapResult` part in an `LdapError`.
    pub fn non_error(self) -> Result<(Exop, LdapResult)> {
        if self.1.rc == 0 || self.1.rc == 10 {
            Ok((self.0, self.1))
        } else {
            Err(LdapError::from(self.1))
        }
    }
}