ldap3/
ldap.rs

1use std::collections::HashSet;
2use std::hash::Hash;
3#[cfg(feature = "gssapi")]
4use std::sync::RwLock;
5use std::sync::{Arc, Mutex};
6use std::time::Duration;
7
8use crate::RequestId;
9use crate::adapters::{EntriesOnly, IntoAdapterVec};
10use crate::controls_impl::IntoRawControlVec;
11use crate::exop::Exop;
12use crate::exop_impl::construct_exop;
13use crate::protocol::{LdapOp, MaybeControls, MiscSender, ResultSender};
14use crate::result::{
15    CompareResult, ExopResult, LdapError, LdapResult, LdapResultExt, Result, SearchResult,
16};
17use crate::search::{Scope, SearchOptions, SearchStream};
18
19use lber::common::TagClass;
20use lber::structures::{Boolean, Enumerated, Integer, Null, OctetString, Sequence, Set, Tag};
21
22#[cfg(feature = "gssapi")]
23use cross_krb5::{ClientCtx, Cred, InitiateFlags, K5Ctx, Step};
24use tokio::sync::{mpsc, oneshot};
25use tokio::time;
26
27/// SASL bind exchange wrapper.
28#[allow(dead_code)]
29#[derive(Clone, Debug)]
30pub(crate) struct SaslCreds(pub Option<Vec<u8>>);
31
32/// Possible sub-operations for the Modify operation.
33#[derive(Clone, Debug, PartialEq)]
34pub enum Mod<S: AsRef<[u8]> + Eq + Hash> {
35    /// Add an attribute, with at least one value.
36    Add(S, HashSet<S>),
37    /// Delete the entire attribute, or the given values of an attribute.
38    Delete(S, HashSet<S>),
39    /// Replace an existing attribute, setting its values to those in the set, or delete it if no values are given.
40    Replace(S, HashSet<S>),
41    /// Increment the attribute by the given value.
42    Increment(S, S),
43}
44
45/// Asynchronous handle for LDAP operations. __*__
46///
47/// All LDAP operations allow attaching a series of request controls, which augment or modify
48/// the operation. Controls are attached by calling [`with_controls()`](#method.with_controls)
49/// on the handle, and using the result to call another modifier or the operation itself.
50/// A timeout can be imposed on an operation by calling [`with_timeout()`](#method.with_timeout)
51/// on the handle before invoking the operation.
52///
53/// The Search operation has many parameters, most of which are infrequently used. Those
54/// parameters can be specified by constructing a [`SearchOptions`](struct.SearchOptions.html)
55/// structure and passing it to [`with_search_options()`](#method.with_search_options)
56/// called on the handle. This method can be combined with `with_controls()` and `with_timeout()`,
57/// described above.
58///
59/// There are two ways to invoke a search. The first, using [`search()`](#method.search),
60/// returns all result entries in a single vector, which works best if it's known that the
61/// result set will be limited. The other way uses [`streaming_search()`](#method.streaming_search),
62/// which accepts the same parameters, but returns a handle which must be used to obtain
63/// result entries one by one.
64///
65/// As a rule, operations return [`LdapResult`](result/struct.LdapResult.html),
66/// a structure of result components. The most important element of `LdapResult`
67/// is the result code, a numeric value indicating the outcome of the operation.
68/// This structure also contains the possibly empty vector of response controls,
69/// which are not directly usable, but must be additionally parsed by the driver- or
70/// user-supplied code.
71///
72/// The handle can be freely cloned. Each clone will multiplex the invoked LDAP operations on
73/// the same underlying connection. Dropping the last handle will automatically close the
74/// connection.
75#[derive(Debug)]
76pub struct Ldap {
77    pub(crate) msgmap: Arc<Mutex<(RequestId, HashSet<RequestId>)>>,
78    pub(crate) tx: mpsc::UnboundedSender<(RequestId, LdapOp, Tag, MaybeControls, ResultSender)>,
79    pub(crate) id_scrub_tx: mpsc::UnboundedSender<RequestId>,
80    pub(crate) misc_tx: mpsc::UnboundedSender<MiscSender>,
81    pub(crate) last_id: RequestId,
82    #[cfg(feature = "gssapi")]
83    pub(crate) sasl_param: Arc<RwLock<(bool, u32)>>, // sasl_wrap, sasl_max_send
84    #[cfg(feature = "gssapi")]
85    pub(crate) client_ctx: Arc<Mutex<Option<ClientCtx>>>,
86    #[cfg(any(feature = "gssapi", feature = "ntlm"))]
87    pub(crate) tls_endpoint_token: Arc<Option<Vec<u8>>>,
88    pub(crate) has_tls: bool,
89    pub timeout: Option<Duration>,
90    pub controls: MaybeControls,
91    pub search_opts: Option<SearchOptions>,
92}
93
94impl Clone for Ldap {
95    fn clone(&self) -> Self {
96        Ldap {
97            msgmap: self.msgmap.clone(),
98            tx: self.tx.clone(),
99            id_scrub_tx: self.id_scrub_tx.clone(),
100            misc_tx: self.misc_tx.clone(),
101            #[cfg(feature = "gssapi")]
102            sasl_param: self.sasl_param.clone(),
103            #[cfg(feature = "gssapi")]
104            client_ctx: self.client_ctx.clone(),
105            #[cfg(any(feature = "gssapi", feature = "ntlm"))]
106            tls_endpoint_token: self.tls_endpoint_token.clone(),
107            has_tls: self.has_tls,
108            last_id: 0,
109            timeout: None,
110            controls: None,
111            search_opts: None,
112        }
113    }
114}
115
116fn sasl_bind_req(mech: &str, creds: Option<&[u8]>) -> Tag {
117    let mut inner_vec = vec![Tag::OctetString(OctetString {
118        inner: Vec::from(mech),
119        ..Default::default()
120    })];
121    if let Some(creds) = creds {
122        inner_vec.push(Tag::OctetString(OctetString {
123            inner: creds.to_vec(),
124            ..Default::default()
125        }));
126    }
127    Tag::Sequence(Sequence {
128        id: 0,
129        class: TagClass::Application,
130        inner: vec![
131            Tag::Integer(Integer {
132                inner: 3,
133                ..Default::default()
134            }),
135            Tag::OctetString(OctetString {
136                inner: Vec::new(),
137                ..Default::default()
138            }),
139            Tag::Sequence(Sequence {
140                id: 3,
141                class: TagClass::Context,
142                inner: inner_vec,
143            }),
144        ],
145    })
146}
147
148#[cfg(feature = "gssapi")]
149enum GssapiCred {
150    Default,
151    Supplied(Cred),
152}
153
154impl Ldap {
155    fn next_msgid(&mut self) -> i32 {
156        let mut msgmap = self.msgmap.lock().expect("msgmap mutex (inc id)");
157        let last_ldap_id = msgmap.0;
158        let mut next_ldap_id = last_ldap_id;
159        loop {
160            if next_ldap_id == std::i32::MAX {
161                next_ldap_id = 1;
162            } else {
163                next_ldap_id += 1;
164            }
165            if !msgmap.1.contains(&next_ldap_id) {
166                break;
167            }
168            assert_ne!(
169                next_ldap_id, last_ldap_id,
170                "LDAP message id wraparound with no free slots"
171            );
172        }
173        msgmap.0 = next_ldap_id;
174        msgmap.1.insert(next_ldap_id);
175        next_ldap_id
176    }
177
178    pub(crate) async fn op_call(
179        &mut self,
180        op: LdapOp,
181        req: Tag,
182    ) -> Result<(LdapResult, Exop, SaslCreds)> {
183        let id = self.next_msgid();
184        self.last_id = id;
185        let (tx, rx) = oneshot::channel();
186        self.tx.send((id, op, req, self.controls.take(), tx))?;
187        let response = if let Some(timeout) = self.timeout.take() {
188            let res = time::timeout(timeout, rx).await;
189            if res.is_err() {
190                self.id_scrub_tx.send(self.last_id)?;
191            }
192            res?
193        } else {
194            rx.await
195        }?;
196        let (ldap_ext, controls) = (LdapResultExt::from(response.0), response.1);
197        let (mut result, exop, sasl_creds) = (ldap_ext.0, ldap_ext.1, ldap_ext.2);
198        result.ctrls = controls;
199        Ok((result, exop, sasl_creds))
200    }
201
202    /// Use the provided `SearchOptions` with the next Search operation, which can
203    /// be invoked directly on the result of this method. If this method is used in
204    /// combination with a non-Search operation, the provided options will be silently
205    /// discarded when the operation is invoked.
206    ///
207    /// The Search operation can be invoked on the result of this method.
208    pub fn with_search_options(&mut self, opts: SearchOptions) -> &mut Self {
209        self.search_opts = Some(opts);
210        self
211    }
212
213    /// Pass the provided request control(s) to the next LDAP operation.
214    /// Controls can be constructed by instantiating structs in the
215    /// [`controls`](controls/index.html) module, and converted to the form needed
216    /// by this method by calling `into()` on the instances. Alternatively, a control
217    /// struct may offer a constructor which will produce a `RawControl` instance
218    /// itself. See the module-level documentation for the list of directly supported
219    /// controls and procedures for defining custom controls.
220    ///
221    /// This method accepts either a control vector or a single `RawControl`. The
222    /// latter is intended to make the call site less noisy, since it's expected
223    /// that passing a single control will comprise the majority of uses.
224    ///
225    /// The desired operation can be invoked on the result of this method.
226    pub fn with_controls<V: IntoRawControlVec>(&mut self, ctrls: V) -> &mut Self {
227        self.controls = Some(ctrls.into());
228        self
229    }
230
231    /// Perform the next operation with the timeout specified in `duration`.
232    /// The LDAP Search operation consists of an indeterminate number of Entry/Referral
233    /// replies; the timer is reset for each reply.
234    ///
235    /// If the timeout occurs, the operation will return an error. The connection remains
236    /// usable for subsequent operations.
237    ///
238    /// The desired operation can be invoked on the result of this method.
239    pub fn with_timeout(&mut self, duration: Duration) -> &mut Self {
240        self.timeout = Some(duration);
241        self
242    }
243
244    /// Do a simple Bind with the provided DN (`bind_dn`) and password (`bind_pw`).
245    pub async fn simple_bind(&mut self, bind_dn: &str, bind_pw: &str) -> Result<LdapResult> {
246        let req = Tag::Sequence(Sequence {
247            id: 0,
248            class: TagClass::Application,
249            inner: vec![
250                Tag::Integer(Integer {
251                    inner: 3,
252                    ..Default::default()
253                }),
254                Tag::OctetString(OctetString {
255                    inner: Vec::from(bind_dn),
256                    ..Default::default()
257                }),
258                Tag::OctetString(OctetString {
259                    id: 0,
260                    class: TagClass::Context,
261                    inner: Vec::from(bind_pw),
262                }),
263            ],
264        });
265        Ok(self.op_call(LdapOp::Single, req).await?.0)
266    }
267
268    /// Do an SASL EXTERNAL bind on the connection. The identity of the client
269    /// must have already been established by connection-specific methods, as
270    /// is the case for Unix domain sockets or TLS client certificates. The bind
271    /// is made with the hardcoded empty authzId value.
272    pub async fn sasl_external_bind(&mut self) -> Result<LdapResult> {
273        let req = sasl_bind_req("EXTERNAL", Some(b""));
274        Ok(self.op_call(LdapOp::Single, req).await?.0)
275    }
276
277    #[cfg_attr(docsrs, doc(cfg(feature = "gssapi")))]
278    #[cfg(feature = "gssapi")]
279    /// Do an SASL GSSAPI bind on the connection, using the default Kerberos credentials
280    /// for the current user and `server_fqdn` for the LDAP server SPN. If the connection
281    /// is in the clear, request and install the Kerberos confidentiality protection
282    /// (i.e., encryption) security layer. If the connection is already encrypted with TLS,
283    /// use Kerberos just for authentication and proceed with no security layer.
284    ///
285    /// On TLS connections, the __tls-server-end-point__ channel binding token will be
286    /// supplied to the server if possible. This enables binding to Active Directory servers
287    /// with the strictest LDAP channel binding enforcement policy.
288    ///
289    /// The underlying GSSAPI libraries issue blocking filesystem and network calls when
290    /// querying the ticket cache or the Kerberos servers. Therefore, the method should not
291    /// be used in heavily concurrent contexts with frequent Bind operations.
292    pub async fn sasl_gssapi_bind(&mut self, server_fqdn: &str) -> Result<LdapResult> {
293        self.gssapi_bind(server_fqdn, GssapiCred::Default).await
294    }
295
296    #[cfg_attr(docsrs, doc(cfg(feature = "gssapi")))]
297    #[cfg(feature = "gssapi")]
298    /// Do an SASL GSSAPI bind on the connection, using the supplied GSSAPI credentials and
299    /// `server_fqdn` for the LDAP server SPN. Aside from using the supplied credentials, this
300    /// method behaves identically as [`gssapi_bind()`](#method.gssapi_bind) (q.v.)
301    pub async fn sasl_gssapi_cred_bind(
302        &mut self,
303        cred: Cred,
304        server_fqdn: &str,
305    ) -> Result<LdapResult> {
306        self.gssapi_bind(server_fqdn, GssapiCred::Supplied(cred))
307            .await
308    }
309
310    #[cfg(feature = "gssapi")]
311    async fn gssapi_bind(&mut self, server_fqdn: &str, cred: GssapiCred) -> Result<LdapResult> {
312        const LDAP_RESULT_SASL_BIND_IN_PROGRESS: u32 = 14;
313        const GSSAUTH_P_NONE: u8 = 1;
314        const GSSAUTH_P_PRIVACY: u8 = 4;
315
316        use either::Either;
317
318        let mut spn = String::from("ldap/");
319        spn.push_str(server_fqdn);
320        let cti = if self.has_tls {
321            let cbt = {
322                let mut cbt = Vec::from(&b"tls-server-end-point:"[..]);
323                if let Some(token) = self.tls_endpoint_token.as_ref() {
324                    cbt.extend(token);
325                    Some(cbt)
326                } else {
327                    None
328                }
329            };
330            match cred {
331                GssapiCred::Default => Either::Left(ClientCtx::new(
332                    InitiateFlags::empty(),
333                    None,
334                    &spn,
335                    cbt.as_deref(),
336                )),
337                GssapiCred::Supplied(cred) => {
338                    Either::Right(ClientCtx::new_with_cred(cred, &spn, cbt.as_deref()))
339                }
340            }
341        } else {
342            match cred {
343                GssapiCred::Default => {
344                    Either::Left(ClientCtx::new(InitiateFlags::empty(), None, &spn, None))
345                }
346                GssapiCred::Supplied(cred) => {
347                    Either::Right(ClientCtx::new_with_cred(cred, &spn, None))
348                }
349            }
350        };
351        let (client_ctx, token) = match cti {
352            Either::Left(cti) => cti
353                .map(|(c, t)| (c, Either::Left(t)))
354                .map_err(|e| LdapError::GssapiOperationError(format!("{:#}", e)))?,
355            Either::Right(cti) => cti
356                .map(|(c, t)| (c, Either::Right(t)))
357                .map_err(|e| LdapError::GssapiOperationError(format!("{:#}", e)))?,
358        };
359        let token = match token {
360            Either::Left(ref t) => t.as_ref(),
361            Either::Right(ref t) => t.as_ref(),
362        };
363        let req = sasl_bind_req("GSSAPI", Some(token));
364        let ans = self.op_call(LdapOp::Single, req).await?;
365        if (ans.0).rc != LDAP_RESULT_SASL_BIND_IN_PROGRESS {
366            return Ok(ans.0);
367        }
368        let token = match (ans.2).0 {
369            Some(token) => token,
370            _ => return Err(LdapError::NoGssapiToken),
371        };
372        let step = client_ctx
373            .step(&token)
374            .map_err(|e| LdapError::GssapiOperationError(format!("{:#}", e)))?;
375        let mut client_ctx = match step {
376            Step::Finished((ctx, None)) => ctx,
377            _ => {
378                return Err(LdapError::GssapiOperationError(String::from(
379                    "GSSAPI exchange not finished or has an additional token",
380                )));
381            }
382        };
383        let req = sasl_bind_req("GSSAPI", None);
384        let ans = self.op_call(LdapOp::Single, req).await?;
385        if (ans.0).rc != LDAP_RESULT_SASL_BIND_IN_PROGRESS {
386            return Ok(ans.0);
387        }
388        let token = match (ans.2).0 {
389            Some(token) => token,
390            _ => return Err(LdapError::NoGssapiToken),
391        };
392        let mut buf = client_ctx
393            .unwrap(&token)
394            .map_err(|e| LdapError::GssapiOperationError(format!("{:#}", e)))?;
395        let needed_layer = if self.has_tls {
396            GSSAUTH_P_NONE
397        } else {
398            GSSAUTH_P_PRIVACY
399        };
400        if buf[0] | needed_layer == 0 {
401            return Err(LdapError::GssapiOperationError(format!(
402                "no appropriate security layer offered: needed {}, mask {}",
403                needed_layer, buf[0]
404            )));
405        }
406        // FIXME: the max_size constant is taken from OpenLDAP GSSAPI code as a fallback
407        // value for broken GSSAPI libraries. It's meant to serve as a safe value until
408        // gss_wrap_size_limit() equivalent is available in cross-krb5.
409        let recv_max_size = (0x9FFFB8u32 | (needed_layer as u32) << 24).to_be_bytes();
410        let size_msg = client_ctx
411            .wrap(true, &recv_max_size)
412            .map_err(|e| LdapError::GssapiOperationError(format!("{:#}", e)))?;
413        let req = sasl_bind_req("GSSAPI", Some(&size_msg));
414        let res = self.op_call(LdapOp::Single, req).await?.0;
415        if res.rc == 0 {
416            if needed_layer == GSSAUTH_P_PRIVACY {
417                buf[0] = 0;
418                let send_max_size =
419                    u32::from_be_bytes((&buf[..]).try_into().expect("send max size"));
420                if send_max_size == 0 {
421                    warn!("got zero send_max_size, will be treated as unlimited");
422                }
423                let mut sasl_param = self.sasl_param.write().expect("sasl param");
424                sasl_param.0 = true;
425                sasl_param.1 = send_max_size;
426            }
427            let client_opt = &mut *self.client_ctx.lock().unwrap();
428            client_opt.replace(client_ctx);
429        }
430        Ok(res)
431    }
432
433    #[cfg_attr(docsrs, doc(cfg(feature = "ntlm")))]
434    #[cfg(feature = "ntlm")]
435    /// Do an SASL GSS-SPNEGO bind with an NTLMSSP exchange on the connection. Username
436    /// and password must be provided, since the method is incapable of retrieving the
437    /// credentials associated with the login session (which would only work on Windows
438    /// anyway.) To specify the domain, incorporate it into the username, using the
439    /// `DOMAIN\user` or `user@DOMAIN` format.
440    ///
441    /// __Caveat:__ the connection cannot encrypted by NTLM "sealing". For encryption, use
442    /// TLS. A channel binding token is automatically sent on a TLS connection, if possible.
443    pub async fn sasl_ntlm_bind(&mut self, username: &str, password: &str) -> Result<LdapResult> {
444        const LDAP_RESULT_SASL_BIND_IN_PROGRESS: u32 = 14;
445
446        use sspi::{
447            AuthIdentity, AuthIdentityBuffers, BufferType, ClientRequestFlags, CredentialUse,
448            DataRepresentation, Ntlm, SecurityBuffer, SecurityStatus, Sspi, SspiImpl, Username,
449            builders::AcquireCredentialsHandleResult,
450        };
451
452        fn step(
453            ntlm: &mut Ntlm,
454            acq_creds: &mut AcquireCredentialsHandleResult<Option<AuthIdentityBuffers>>,
455            input: &[u8],
456        ) -> Result<Vec<u8>> {
457            let mut input = vec![SecurityBuffer::new(input.to_vec(), BufferType::Token)];
458            let mut output = vec![SecurityBuffer::new(Vec::new(), BufferType::Token)];
459            let mut builder = ntlm
460                .initialize_security_context()
461                .with_credentials_handle(&mut acq_creds.credentials_handle)
462                .with_context_requirements(ClientRequestFlags::ALLOCATE_MEMORY)
463                .with_target_data_representation(DataRepresentation::Native)
464                .with_input(&mut input)
465                .with_output(&mut output);
466            let result = ntlm
467                .initialize_security_context_impl(&mut builder)?
468                .resolve_to_result()?;
469            match result.status {
470                SecurityStatus::CompleteNeeded | SecurityStatus::CompleteAndContinue => {
471                    ntlm.complete_auth_token(&mut output)?
472                }
473                s => s,
474            };
475            Ok(output.swap_remove(0).buffer)
476        }
477
478        let mut ntlm = Ntlm::new();
479        let identity = AuthIdentity {
480            username: Username::parse(username).unwrap(),
481            password: password.to_string().into(),
482        };
483        let mut acq_creds = ntlm
484            .acquire_credentials_handle()
485            .with_credential_use(CredentialUse::Outbound)
486            .with_auth_data(&identity)
487            .execute(&mut ntlm)?;
488        let req = sasl_bind_req("GSS-SPNEGO", Some(&step(&mut ntlm, &mut acq_creds, &[])?));
489        let (res, _, token) = self.op_call(LdapOp::Single, req).await?;
490        if res.rc != LDAP_RESULT_SASL_BIND_IN_PROGRESS {
491            return Ok(res);
492        }
493        let token = match token.0 {
494            Some(token) => token,
495            _ => return Err(LdapError::NoNtlmChallengeToken),
496        };
497        if self.has_tls {
498            let mut cbt = Vec::from(&b"tls-server-end-point:"[..]);
499            if let Some(token) = self.tls_endpoint_token.as_ref() {
500                cbt.extend(token);
501                ntlm.set_channel_bindings(&cbt);
502            }
503        }
504        let req = sasl_bind_req(
505            "GSS-SPNEGO",
506            Some(&step(&mut ntlm, &mut acq_creds, &token)?),
507        );
508        Ok(self.op_call(LdapOp::Single, req).await?.0)
509    }
510
511    /// Perform a Search with the given base DN (`base`), scope, filter, and
512    /// the list of attributes to be returned (`attrs`). If `attrs` is empty,
513    /// or if it contains a special name `*` (asterisk), return all (user) attributes.
514    /// Requesting a special name `+` (plus sign) will return all operational
515    /// attributes. Include both `*` and `+` in order to return all attributes
516    /// of an entry.
517    ///
518    /// The returned structure wraps the vector of result entries and the overall
519    /// result of the operation. Entries are not directly usable, and must be parsed by
520    /// [`SearchEntry::construct()`](struct.SearchEntry.html#method.construct). All
521    /// referrals in the result stream will be collected in the `refs` vector of the
522    /// operation result. Any intermediate messages will be discarded.
523    ///
524    /// This method should be used if it's known that the result set won't be
525    /// large. For other situations, one can use [`streaming_search()`](#method.streaming_search).
526    pub async fn search<'a, S: AsRef<str> + Send + Sync + 'a, A: AsRef<[S]> + Send + Sync + 'a>(
527        &mut self,
528        base: &str,
529        scope: Scope,
530        filter: &str,
531        attrs: A,
532    ) -> Result<SearchResult> {
533        let mut stream = self
534            .streaming_search_with(EntriesOnly::new(), base, scope, filter, attrs)
535            .await?;
536        let mut re_vec = vec![];
537        while let Some(entry) = stream.next().await? {
538            re_vec.push(entry);
539        }
540        let res = stream.finish().await;
541        Ok(SearchResult(re_vec, res))
542    }
543
544    /// Perform a Search, but unlike [`search()`](#method.search) (q.v., also for
545    /// the parameters), which returns all results at once, return a handle which
546    /// will be used for retrieving entries one by one. See [`SearchStream`](struct.SearchStream.html)
547    /// for the explanation of the protocol which must be adhered to in this case.
548    pub async fn streaming_search<
549        'a,
550        S: AsRef<str> + Send + Sync + 'a,
551        A: AsRef<[S]> + Send + Sync + 'a,
552    >(
553        &mut self,
554        base: &str,
555        scope: Scope,
556        filter: &str,
557        attrs: A,
558    ) -> Result<SearchStream<'a, S, A>> {
559        self.streaming_search_with(vec![], base, scope, filter, attrs)
560            .await
561    }
562
563    /// Perform a streaming Search internally modified by a chain of [adapters](adapters/index.html).
564    /// The first argument can either be a struct implementing `Adapter`, if a single adapter is needed,
565    /// or a vector of boxed `Adapter` trait objects.
566    pub async fn streaming_search_with<
567        'a,
568        V: IntoAdapterVec<'a, S, A>,
569        S: AsRef<str> + Send + Sync + 'a,
570        A: AsRef<[S]> + Send + Sync + 'a,
571    >(
572        &mut self,
573        adapters: V,
574        base: &str,
575        scope: Scope,
576        filter: &str,
577        attrs: A,
578    ) -> Result<SearchStream<'a, S, A>> {
579        let mut ldap = self.clone();
580        ldap.controls = self.controls.take();
581        ldap.timeout = self.timeout.take();
582        ldap.search_opts = self.search_opts.take();
583        let mut stream = SearchStream::new(ldap, adapters.into());
584        stream.start(base, scope, filter, attrs).await?;
585        Ok(stream)
586    }
587
588    /// Add an entry named by `dn`, with the list of attributes and their values
589    /// given in `attrs`. None of the `HashSet`s of values for an attribute may
590    /// be empty.
591    pub async fn add<S: AsRef<[u8]> + Eq + Hash>(
592        &mut self,
593        dn: &str,
594        attrs: Vec<(S, HashSet<S>)>,
595    ) -> Result<LdapResult> {
596        let mut any_empty = false;
597        let req = Tag::Sequence(Sequence {
598            id: 8,
599            class: TagClass::Application,
600            inner: vec![
601                Tag::OctetString(OctetString {
602                    inner: Vec::from(dn.as_bytes()),
603                    ..Default::default()
604                }),
605                Tag::Sequence(Sequence {
606                    inner: attrs
607                        .into_iter()
608                        .map(|(name, vals)| {
609                            if vals.is_empty() {
610                                any_empty = true;
611                            }
612                            Tag::Sequence(Sequence {
613                                inner: vec![
614                                    Tag::OctetString(OctetString {
615                                        inner: Vec::from(name.as_ref()),
616                                        ..Default::default()
617                                    }),
618                                    Tag::Set(Set {
619                                        inner: vals
620                                            .into_iter()
621                                            .map(|v| {
622                                                Tag::OctetString(OctetString {
623                                                    inner: Vec::from(v.as_ref()),
624                                                    ..Default::default()
625                                                })
626                                            })
627                                            .collect(),
628                                        ..Default::default()
629                                    }),
630                                ],
631                                ..Default::default()
632                            })
633                        })
634                        .collect(),
635                    ..Default::default()
636                }),
637            ],
638        });
639        if any_empty {
640            return Err(LdapError::AddNoValues);
641        }
642        Ok(self.op_call(LdapOp::Single, req).await?.0)
643    }
644
645    /// Compare the value(s) of the attribute `attr` within an entry named by `dn` with the
646    /// value `val`. If any of the values is identical to the provided one, return result code 5
647    /// (`compareTrue`), otherwise return result code 6 (`compareFalse`). If access control
648    /// rules on the server disallow comparison, another result code will be used to indicate
649    /// an error.
650    pub async fn compare<B: AsRef<[u8]>>(
651        &mut self,
652        dn: &str,
653        attr: &str,
654        val: B,
655    ) -> Result<CompareResult> {
656        let req = Tag::Sequence(Sequence {
657            id: 14,
658            class: TagClass::Application,
659            inner: vec![
660                Tag::OctetString(OctetString {
661                    inner: Vec::from(dn.as_bytes()),
662                    ..Default::default()
663                }),
664                Tag::Sequence(Sequence {
665                    inner: vec![
666                        Tag::OctetString(OctetString {
667                            inner: Vec::from(attr.as_bytes()),
668                            ..Default::default()
669                        }),
670                        Tag::OctetString(OctetString {
671                            inner: Vec::from(val.as_ref()),
672                            ..Default::default()
673                        }),
674                    ],
675                    ..Default::default()
676                }),
677            ],
678        });
679        Ok(CompareResult(self.op_call(LdapOp::Single, req).await?.0))
680    }
681
682    /// Delete an entry named by `dn`.
683    pub async fn delete(&mut self, dn: &str) -> Result<LdapResult> {
684        let req = Tag::OctetString(OctetString {
685            id: 10,
686            class: TagClass::Application,
687            inner: Vec::from(dn.as_bytes()),
688        });
689        Ok(self.op_call(LdapOp::Single, req).await?.0)
690    }
691
692    /// Modify an entry named by `dn` by sequentially applying the modifications given by `mods`.
693    /// See the [`Mod`](enum.Mod.html) documentation for the description of possible values.
694    pub async fn modify<S: AsRef<[u8]> + Eq + Hash>(
695        &mut self,
696        dn: &str,
697        mods: Vec<Mod<S>>,
698    ) -> Result<LdapResult> {
699        let mut any_add_empty = false;
700        let req = Tag::Sequence(Sequence {
701            id: 6,
702            class: TagClass::Application,
703            inner: vec![
704                Tag::OctetString(OctetString {
705                    inner: Vec::from(dn.as_bytes()),
706                    ..Default::default()
707                }),
708                Tag::Sequence(Sequence {
709                    inner: mods
710                        .into_iter()
711                        .map(|m| {
712                            let mut is_add = false;
713                            let (num, attr, set) = match m {
714                                Mod::Add(attr, set) => {
715                                    is_add = true;
716                                    (0, attr, set)
717                                }
718                                Mod::Delete(attr, set) => (1, attr, set),
719                                Mod::Replace(attr, set) => (2, attr, set),
720                                Mod::Increment(attr, val) => (3, attr, HashSet::from([val])),
721                            };
722                            if set.is_empty() && is_add {
723                                any_add_empty = true;
724                            }
725                            let op = Tag::Enumerated(Enumerated {
726                                inner: num,
727                                ..Default::default()
728                            });
729                            let part_attr = Tag::Sequence(Sequence {
730                                inner: vec![
731                                    Tag::OctetString(OctetString {
732                                        inner: Vec::from(attr.as_ref()),
733                                        ..Default::default()
734                                    }),
735                                    Tag::Set(Set {
736                                        inner: set
737                                            .into_iter()
738                                            .map(|val| {
739                                                Tag::OctetString(OctetString {
740                                                    inner: Vec::from(val.as_ref()),
741                                                    ..Default::default()
742                                                })
743                                            })
744                                            .collect(),
745                                        ..Default::default()
746                                    }),
747                                ],
748                                ..Default::default()
749                            });
750                            Tag::Sequence(Sequence {
751                                inner: vec![op, part_attr],
752                                ..Default::default()
753                            })
754                        })
755                        .collect(),
756                    ..Default::default()
757                }),
758            ],
759        });
760        if any_add_empty {
761            return Err(LdapError::AddNoValues);
762        }
763        Ok(self.op_call(LdapOp::Single, req).await?.0)
764    }
765
766    /// Rename and/or move an entry named by `dn`. The new name is given by `rdn`. If
767    /// `delete_old` is `true`, delete the previous value of the naming attribute from
768    /// the entry. If the entry is to be moved elsewhere in the DIT, `new_sup` gives
769    /// the new superior entry where the moved entry will be anchored.
770    pub async fn modifydn(
771        &mut self,
772        dn: &str,
773        rdn: &str,
774        delete_old: bool,
775        new_sup: Option<&str>,
776    ) -> Result<LdapResult> {
777        let mut params = vec![
778            Tag::OctetString(OctetString {
779                inner: Vec::from(dn.as_bytes()),
780                ..Default::default()
781            }),
782            Tag::OctetString(OctetString {
783                inner: Vec::from(rdn.as_bytes()),
784                ..Default::default()
785            }),
786            Tag::Boolean(Boolean {
787                inner: delete_old,
788                ..Default::default()
789            }),
790        ];
791        if let Some(new_sup) = new_sup {
792            params.push(Tag::OctetString(OctetString {
793                id: 0,
794                class: TagClass::Context,
795                inner: Vec::from(new_sup.as_bytes()),
796            }));
797        }
798        let req = Tag::Sequence(Sequence {
799            id: 12,
800            class: TagClass::Application,
801            inner: params,
802        });
803        Ok(self.op_call(LdapOp::Single, req).await?.0)
804    }
805
806    /// Perform an Extended operation given by `exop`. Extended operations are defined in the
807    /// [`exop`](exop/index.html) module. See the module-level documentation for the list of extended
808    /// operations supported by this library and procedures for defining custom exops.
809    pub async fn extended<E>(&mut self, exop: E) -> Result<ExopResult>
810    where
811        E: Into<Exop>,
812    {
813        let req = Tag::Sequence(Sequence {
814            id: 23,
815            class: TagClass::Application,
816            inner: construct_exop(exop.into()),
817        });
818        self.op_call(LdapOp::Single, req)
819            .await
820            .map(|et| ExopResult(et.1, et.0))
821    }
822
823    /// Terminate the connection to the server.
824    pub async fn unbind(&mut self) -> Result<()> {
825        let req = Tag::Null(Null {
826            id: 2,
827            class: TagClass::Application,
828            inner: (),
829        });
830        self.op_call(LdapOp::Unbind, req).await.map(|_| ())
831    }
832
833    /// Return the message ID of the last active operation. When the handle is initialized, this
834    /// value is set to zero. The intended use is to obtain the ID of a timed out operation for
835    /// passing it to an Abandon or Cancel operation.
836    ///
837    /// Using this method in the `start()` adapter chain of a streaming Search will return zero,
838    /// since the Message ID is obtained in the inner `start()` method.
839    pub fn last_id(&mut self) -> RequestId {
840        self.last_id
841    }
842
843    /// Ask the server to abandon an operation identified by `msgid`.
844    pub async fn abandon(&mut self, msgid: RequestId) -> Result<()> {
845        let req = Tag::Integer(Integer {
846            id: 16,
847            class: TagClass::Application,
848            inner: msgid as i64,
849        });
850        self.op_call(LdapOp::Abandon(msgid), req).await.map(|_| ())
851    }
852
853    /// Check whether the underlying connection has been closed.
854    ///
855    /// This is an indirect check: it queries the status of the channel for communicating with
856    /// the connection structure, not the connection socket itself. The channel being open
857    /// does not mean there is bidirecional communication with the server; to check for that,
858    /// a round-trip operation (e.g., `WhoAmI`) would be necessary.
859    pub fn is_closed(&mut self) -> bool {
860        self.tx.is_closed()
861    }
862
863    /// Return the TLS peer certificate in DER format.
864    ///
865    /// The method returns Ok(None) if no certificate was found or
866    /// the connection does not use or support TLS.
867    pub async fn get_peer_certificate(&mut self) -> Result<Option<Vec<u8>>> {
868        #[cfg(any(feature = "tls-native", feature = "tls-rustls"))]
869        {
870            let (tx, rx) = oneshot::channel();
871            self.misc_tx.send(MiscSender::Cert(tx))?;
872            Ok(rx.await?)
873        }
874        #[cfg(not(any(feature = "tls-native", feature = "tls-rustls")))]
875        {
876            Ok(None)
877        }
878    }
879}