ldap3_serde/
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::adapters::{EntriesOnly, IntoAdapterVec};
9use crate::controls_impl::IntoRawControlVec;
10use crate::exop::Exop;
11use crate::exop_impl::construct_exop;
12use crate::protocol::{LdapOp, MaybeControls, MiscSender, ResultSender};
13use crate::result::{
14    CompareResult, ExopResult, LdapError, LdapResult, LdapResultExt, Result, SearchResult,
15};
16use crate::search::{Scope, SearchOptions, SearchStream};
17use crate::RequestId;
18
19use lber_serde::common::TagClass;
20use lber_serde::structures::{Boolean, Enumerated, Integer, Null, OctetString, Sequence, Set, Tag};
21
22#[cfg(feature = "gssapi")]
23use cross_krb5::{ClientCtx, 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(feature = "gssapi")]
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(feature = "gssapi")]
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
148impl Ldap {
149    fn next_msgid(&mut self) -> i32 {
150        let mut msgmap = self.msgmap.lock().expect("msgmap mutex (inc id)");
151        let last_ldap_id = msgmap.0;
152        let mut next_ldap_id = last_ldap_id;
153        loop {
154            if next_ldap_id == std::i32::MAX {
155                next_ldap_id = 1;
156            } else {
157                next_ldap_id += 1;
158            }
159            if !msgmap.1.contains(&next_ldap_id) {
160                break;
161            }
162            assert_ne!(
163                next_ldap_id, last_ldap_id,
164                "LDAP message id wraparound with no free slots"
165            );
166        }
167        msgmap.0 = next_ldap_id;
168        msgmap.1.insert(next_ldap_id);
169        next_ldap_id
170    }
171
172    pub(crate) async fn op_call(
173        &mut self,
174        op: LdapOp,
175        req: Tag,
176    ) -> Result<(LdapResult, Exop, SaslCreds)> {
177        let id = self.next_msgid();
178        self.last_id = id;
179        let (tx, rx) = oneshot::channel();
180        self.tx.send((id, op, req, self.controls.take(), tx))?;
181        let response = if let Some(timeout) = self.timeout.take() {
182            let res = time::timeout(timeout, rx).await;
183            if res.is_err() {
184                self.id_scrub_tx.send(self.last_id)?;
185            }
186            res?
187        } else {
188            rx.await
189        }?;
190        let (ldap_ext, controls) = (LdapResultExt::from(response.0), response.1);
191        let (mut result, exop, sasl_creds) = (ldap_ext.0, ldap_ext.1, ldap_ext.2);
192        result.ctrls = controls;
193        Ok((result, exop, sasl_creds))
194    }
195
196    /// Use the provided `SearchOptions` with the next Search operation, which can
197    /// be invoked directly on the result of this method. If this method is used in
198    /// combination with a non-Search operation, the provided options will be silently
199    /// discarded when the operation is invoked.
200    ///
201    /// The Search operation can be invoked on the result of this method.
202    pub fn with_search_options(&mut self, opts: SearchOptions) -> &mut Self {
203        self.search_opts = Some(opts);
204        self
205    }
206
207    /// Pass the provided request control(s) to the next LDAP operation.
208    /// Controls can be constructed by instantiating structs in the
209    /// [`controls`](controls/index.html) module, and converted to the form needed
210    /// by this method by calling `into()` on the instances. Alternatively, a control
211    /// struct may offer a constructor which will produce a `RawControl` instance
212    /// itself. See the module-level documentation for the list of directly supported
213    /// controls and procedures for defining custom controls.
214    ///
215    /// This method accepts either a control vector or a single `RawControl`. The
216    /// latter is intended to make the call site less noisy, since it's expected
217    /// that passing a single control will comprise the majority of uses.
218    ///
219    /// The desired operation can be invoked on the result of this method.
220    pub fn with_controls<V: IntoRawControlVec>(&mut self, ctrls: V) -> &mut Self {
221        self.controls = Some(ctrls.into());
222        self
223    }
224
225    /// Perform the next operation with the timeout specified in `duration`.
226    /// The LDAP Search operation consists of an indeterminate number of Entry/Referral
227    /// replies; the timer is reset for each reply.
228    ///
229    /// If the timeout occurs, the operation will return an error. The connection remains
230    /// usable for subsequent operations.
231    ///
232    /// The desired operation can be invoked on the result of this method.
233    pub fn with_timeout(&mut self, duration: Duration) -> &mut Self {
234        self.timeout = Some(duration);
235        self
236    }
237
238    /// Do a simple Bind with the provided DN (`bind_dn`) and password (`bind_pw`).
239    pub async fn simple_bind(&mut self, bind_dn: &str, bind_pw: &str) -> Result<LdapResult> {
240        let req = Tag::Sequence(Sequence {
241            id: 0,
242            class: TagClass::Application,
243            inner: vec![
244                Tag::Integer(Integer {
245                    inner: 3,
246                    ..Default::default()
247                }),
248                Tag::OctetString(OctetString {
249                    inner: Vec::from(bind_dn),
250                    ..Default::default()
251                }),
252                Tag::OctetString(OctetString {
253                    id: 0,
254                    class: TagClass::Context,
255                    inner: Vec::from(bind_pw),
256                }),
257            ],
258        });
259        Ok(self.op_call(LdapOp::Single, req).await?.0)
260    }
261
262    /// Do an SASL EXTERNAL bind on the connection. The identity of the client
263    /// must have already been established by connection-specific methods, as
264    /// is the case for Unix domain sockets or TLS client certificates. The bind
265    /// is made with the hardcoded empty authzId value.
266    pub async fn sasl_external_bind(&mut self) -> Result<LdapResult> {
267        let req = sasl_bind_req("EXTERNAL", Some(b""));
268        Ok(self.op_call(LdapOp::Single, req).await?.0)
269    }
270
271    #[cfg_attr(docsrs, doc(cfg(feature = "gssapi")))]
272    #[cfg(feature = "gssapi")]
273    /// Do an SASL GSSAPI bind on the connection, using the default Kerberos credentials
274    /// for the current user and `server_fqdn` for the LDAP server SPN. If the connection
275    /// is in the clear, request and install the Kerberos confidentiality protection
276    /// (i.e., encryption) security layer. If the connection is already encrypted with TLS,
277    /// use Kerberos just for authentication and proceed with no security layer.
278    ///
279    /// On TLS connections, the __tls-server-end-point__ channel binding token will be
280    /// supplied to the server if possible. This enables binding to Active Directory servers
281    /// with the strictest LDAP channel binding enforcement policy.
282    ///
283    /// The underlying GSSAPI libraries issue blocking filesystem and network calls when
284    /// querying the ticket cache or the Kerberos servers. Therefore, the method should not
285    /// be used in heavily concurrent contexts with frequent Bind operations.
286    pub async fn sasl_gssapi_bind(&mut self, server_fqdn: &str) -> Result<LdapResult> {
287        const LDAP_RESULT_SASL_BIND_IN_PROGRESS: u32 = 14;
288        const GSSAUTH_P_NONE: u8 = 1;
289        const GSSAUTH_P_PRIVACY: u8 = 4;
290
291        let mut spn = String::from("ldap/");
292        spn.push_str(server_fqdn);
293        let cti = if self.has_tls {
294            let cbt = {
295                let mut cbt = Vec::from(&b"tls-server-end-point:"[..]);
296                if let Some(ref token) = self.tls_endpoint_token.as_ref() {
297                    cbt.extend(token);
298                    Some(cbt)
299                } else {
300                    None
301                }
302            };
303            ClientCtx::new(InitiateFlags::empty(), None, &spn, cbt.as_deref())
304        } else {
305            ClientCtx::new(InitiateFlags::empty(), None, &spn, None)
306        };
307        let (client_ctx, token) =
308            cti.map_err(|e| LdapError::GssapiOperationError(format!("{:#}", e)))?;
309        let req = sasl_bind_req("GSSAPI", Some(&token));
310        let ans = self.op_call(LdapOp::Single, req).await?;
311        if (ans.0).rc != LDAP_RESULT_SASL_BIND_IN_PROGRESS {
312            return Ok(ans.0);
313        }
314        let token = match (ans.2).0 {
315            Some(token) => token,
316            _ => return Err(LdapError::NoGssapiToken),
317        };
318        let step = client_ctx
319            .step(&token)
320            .map_err(|e| LdapError::GssapiOperationError(format!("{:#}", e)))?;
321        let mut client_ctx = match step {
322            Step::Finished((ctx, None)) => ctx,
323            _ => {
324                return Err(LdapError::GssapiOperationError(String::from(
325                    "GSSAPI exchange not finished or has an additional token",
326                )))
327            }
328        };
329        let req = sasl_bind_req("GSSAPI", None);
330        let ans = self.op_call(LdapOp::Single, req).await?;
331        if (ans.0).rc != LDAP_RESULT_SASL_BIND_IN_PROGRESS {
332            return Ok(ans.0);
333        }
334        let token = match (ans.2).0 {
335            Some(token) => token,
336            _ => return Err(LdapError::NoGssapiToken),
337        };
338        let mut buf = client_ctx
339            .unwrap(&token)
340            .map_err(|e| LdapError::GssapiOperationError(format!("{:#}", e)))?;
341        let needed_layer = if self.has_tls {
342            GSSAUTH_P_NONE
343        } else {
344            GSSAUTH_P_PRIVACY
345        };
346        if buf[0] | needed_layer == 0 {
347            return Err(LdapError::GssapiOperationError(format!(
348                "no appropriate security layer offered: needed {}, mask {}",
349                needed_layer, buf[0]
350            )));
351        }
352        // FIXME: the max_size constant is taken from OpenLDAP GSSAPI code as a fallback
353        // value for broken GSSAPI libraries. It's meant to serve as a safe value until
354        // gss_wrap_size_limit() equivalent is available in cross-krb5.
355        let recv_max_size = (0x9FFFB8u32 | (needed_layer as u32) << 24).to_be_bytes();
356        let size_msg = client_ctx
357            .wrap(true, &recv_max_size)
358            .map_err(|e| LdapError::GssapiOperationError(format!("{:#}", e)))?;
359        let req = sasl_bind_req("GSSAPI", Some(&size_msg));
360        let res = self.op_call(LdapOp::Single, req).await?.0;
361        if res.rc == 0 {
362            if needed_layer == GSSAUTH_P_PRIVACY {
363                buf[0] = 0;
364                let send_max_size =
365                    u32::from_be_bytes((&buf[..]).try_into().expect("send max size"));
366                if send_max_size == 0 {
367                    warn!("got zero send_max_size, will be treated as unlimited");
368                }
369                let mut sasl_param = self.sasl_param.write().expect("sasl param");
370                sasl_param.0 = true;
371                sasl_param.1 = send_max_size;
372            }
373            let client_opt = &mut *self.client_ctx.lock().unwrap();
374            client_opt.replace(client_ctx);
375        }
376        Ok(res)
377    }
378
379    /// Perform a Search with the given base DN (`base`), scope, filter, and
380    /// the list of attributes to be returned (`attrs`). If `attrs` is empty,
381    /// or if it contains a special name `*` (asterisk), return all (user) attributes.
382    /// Requesting a special name `+` (plus sign) will return all operational
383    /// attributes. Include both `*` and `+` in order to return all attributes
384    /// of an entry.
385    ///
386    /// The returned structure wraps the vector of result entries and the overall
387    /// result of the operation. Entries are not directly usable, and must be parsed by
388    /// [`SearchEntry::construct()`](struct.SearchEntry.html#method.construct). All
389    /// referrals in the result stream will be collected in the `refs` vector of the
390    /// operation result. Any intermediate messages will be discarded.
391    ///
392    /// This method should be used if it's known that the result set won't be
393    /// large. For other situations, one can use [`streaming_search()`](#method.streaming_search).
394    pub async fn search<'a, S: AsRef<str> + Send + Sync + 'a, A: AsRef<[S]> + Send + Sync + 'a>(
395        &mut self,
396        base: &str,
397        scope: Scope,
398        filter: &str,
399        attrs: A,
400    ) -> Result<SearchResult> {
401        let mut stream = self
402            .streaming_search_with(EntriesOnly::new(), base, scope, filter, attrs)
403            .await?;
404        let mut re_vec = vec![];
405        while let Some(entry) = stream.next().await? {
406            re_vec.push(entry);
407        }
408        let res = stream.finish().await;
409        Ok(SearchResult(re_vec, res))
410    }
411
412    /// Perform a Search, but unlike [`search()`](#method.search) (q.v., also for
413    /// the parameters), which returns all results at once, return a handle which
414    /// will be used for retrieving entries one by one. See [`SearchStream`](struct.SearchStream.html)
415    /// for the explanation of the protocol which must be adhered to in this case.
416    pub async fn streaming_search<
417        'a,
418        S: AsRef<str> + Send + Sync + 'a,
419        A: AsRef<[S]> + Send + Sync + 'a,
420    >(
421        &mut self,
422        base: &str,
423        scope: Scope,
424        filter: &str,
425        attrs: A,
426    ) -> Result<SearchStream<'a, S, A>> {
427        self.streaming_search_with(vec![], base, scope, filter, attrs)
428            .await
429    }
430
431    /// Perform a streaming Search internally modified by a chain of [adapters](adapters/index.html).
432    /// The first argument can either be a struct implementing `Adapter`, if a single adapter is needed,
433    /// or a vector of boxed `Adapter` trait objects.
434    pub async fn streaming_search_with<
435        'a,
436        V: IntoAdapterVec<'a, S, A>,
437        S: AsRef<str> + Send + Sync + 'a,
438        A: AsRef<[S]> + Send + Sync + 'a,
439    >(
440        &mut self,
441        adapters: V,
442        base: &str,
443        scope: Scope,
444        filter: &str,
445        attrs: A,
446    ) -> Result<SearchStream<'a, S, A>> {
447        let mut ldap = self.clone();
448        ldap.controls = self.controls.take();
449        ldap.timeout = self.timeout.take();
450        ldap.search_opts = self.search_opts.take();
451        let mut stream = SearchStream::new(ldap, adapters.into());
452        stream.start(base, scope, filter, attrs).await?;
453        Ok(stream)
454    }
455
456    /// Add an entry named by `dn`, with the list of attributes and their values
457    /// given in `attrs`. None of the `HashSet`s of values for an attribute may
458    /// be empty.
459    pub async fn add<S: AsRef<[u8]> + Eq + Hash>(
460        &mut self,
461        dn: &str,
462        attrs: Vec<(S, HashSet<S>)>,
463    ) -> Result<LdapResult> {
464        let mut any_empty = false;
465        let req = Tag::Sequence(Sequence {
466            id: 8,
467            class: TagClass::Application,
468            inner: vec![
469                Tag::OctetString(OctetString {
470                    inner: Vec::from(dn.as_bytes()),
471                    ..Default::default()
472                }),
473                Tag::Sequence(Sequence {
474                    inner: attrs
475                        .into_iter()
476                        .map(|(name, vals)| {
477                            if vals.is_empty() {
478                                any_empty = true;
479                            }
480                            Tag::Sequence(Sequence {
481                                inner: vec![
482                                    Tag::OctetString(OctetString {
483                                        inner: Vec::from(name.as_ref()),
484                                        ..Default::default()
485                                    }),
486                                    Tag::Set(Set {
487                                        inner: vals
488                                            .into_iter()
489                                            .map(|v| {
490                                                Tag::OctetString(OctetString {
491                                                    inner: Vec::from(v.as_ref()),
492                                                    ..Default::default()
493                                                })
494                                            })
495                                            .collect(),
496                                        ..Default::default()
497                                    }),
498                                ],
499                                ..Default::default()
500                            })
501                        })
502                        .collect(),
503                    ..Default::default()
504                }),
505            ],
506        });
507        if any_empty {
508            return Err(LdapError::AddNoValues);
509        }
510        Ok(self.op_call(LdapOp::Single, req).await?.0)
511    }
512
513    /// Compare the value(s) of the attribute `attr` within an entry named by `dn` with the
514    /// value `val`. If any of the values is identical to the provided one, return result code 5
515    /// (`compareTrue`), otherwise return result code 6 (`compareFalse`). If access control
516    /// rules on the server disallow comparison, another result code will be used to indicate
517    /// an error.
518    pub async fn compare<B: AsRef<[u8]>>(
519        &mut self,
520        dn: &str,
521        attr: &str,
522        val: B,
523    ) -> Result<CompareResult> {
524        let req = Tag::Sequence(Sequence {
525            id: 14,
526            class: TagClass::Application,
527            inner: vec![
528                Tag::OctetString(OctetString {
529                    inner: Vec::from(dn.as_bytes()),
530                    ..Default::default()
531                }),
532                Tag::Sequence(Sequence {
533                    inner: vec![
534                        Tag::OctetString(OctetString {
535                            inner: Vec::from(attr.as_bytes()),
536                            ..Default::default()
537                        }),
538                        Tag::OctetString(OctetString {
539                            inner: Vec::from(val.as_ref()),
540                            ..Default::default()
541                        }),
542                    ],
543                    ..Default::default()
544                }),
545            ],
546        });
547        Ok(CompareResult(self.op_call(LdapOp::Single, req).await?.0))
548    }
549
550    /// Delete an entry named by `dn`.
551    pub async fn delete(&mut self, dn: &str) -> Result<LdapResult> {
552        let req = Tag::OctetString(OctetString {
553            id: 10,
554            class: TagClass::Application,
555            inner: Vec::from(dn.as_bytes()),
556        });
557        Ok(self.op_call(LdapOp::Single, req).await?.0)
558    }
559
560    /// Modify an entry named by `dn` by sequentially applying the modifications given by `mods`.
561    /// See the [`Mod`](enum.Mod.html) documentation for the description of possible values.
562    pub async fn modify<S: AsRef<[u8]> + Eq + Hash>(
563        &mut self,
564        dn: &str,
565        mods: Vec<Mod<S>>,
566    ) -> Result<LdapResult> {
567        let mut any_add_empty = false;
568        let req = Tag::Sequence(Sequence {
569            id: 6,
570            class: TagClass::Application,
571            inner: vec![
572                Tag::OctetString(OctetString {
573                    inner: Vec::from(dn.as_bytes()),
574                    ..Default::default()
575                }),
576                Tag::Sequence(Sequence {
577                    inner: mods
578                        .into_iter()
579                        .map(|m| {
580                            let mut is_add = false;
581                            let (num, attr, set) = match m {
582                                Mod::Add(attr, set) => {
583                                    is_add = true;
584                                    (0, attr, set)
585                                }
586                                Mod::Delete(attr, set) => (1, attr, set),
587                                Mod::Replace(attr, set) => (2, attr, set),
588                                Mod::Increment(attr, val) => (3, attr, HashSet::from([val])),
589                            };
590                            if set.is_empty() && is_add {
591                                any_add_empty = true;
592                            }
593                            let op = Tag::Enumerated(Enumerated {
594                                inner: num,
595                                ..Default::default()
596                            });
597                            let part_attr = Tag::Sequence(Sequence {
598                                inner: vec![
599                                    Tag::OctetString(OctetString {
600                                        inner: Vec::from(attr.as_ref()),
601                                        ..Default::default()
602                                    }),
603                                    Tag::Set(Set {
604                                        inner: set
605                                            .into_iter()
606                                            .map(|val| {
607                                                Tag::OctetString(OctetString {
608                                                    inner: Vec::from(val.as_ref()),
609                                                    ..Default::default()
610                                                })
611                                            })
612                                            .collect(),
613                                        ..Default::default()
614                                    }),
615                                ],
616                                ..Default::default()
617                            });
618                            Tag::Sequence(Sequence {
619                                inner: vec![op, part_attr],
620                                ..Default::default()
621                            })
622                        })
623                        .collect(),
624                    ..Default::default()
625                }),
626            ],
627        });
628        if any_add_empty {
629            return Err(LdapError::AddNoValues);
630        }
631        Ok(self.op_call(LdapOp::Single, req).await?.0)
632    }
633
634    /// Rename and/or move an entry named by `dn`. The new name is given by `rdn`. If
635    /// `delete_old` is `true`, delete the previous value of the naming attribute from
636    /// the entry. If the entry is to be moved elsewhere in the DIT, `new_sup` gives
637    /// the new superior entry where the moved entry will be anchored.
638    pub async fn modifydn(
639        &mut self,
640        dn: &str,
641        rdn: &str,
642        delete_old: bool,
643        new_sup: Option<&str>,
644    ) -> Result<LdapResult> {
645        let mut params = vec![
646            Tag::OctetString(OctetString {
647                inner: Vec::from(dn.as_bytes()),
648                ..Default::default()
649            }),
650            Tag::OctetString(OctetString {
651                inner: Vec::from(rdn.as_bytes()),
652                ..Default::default()
653            }),
654            Tag::Boolean(Boolean {
655                inner: delete_old,
656                ..Default::default()
657            }),
658        ];
659        if let Some(new_sup) = new_sup {
660            params.push(Tag::OctetString(OctetString {
661                id: 0,
662                class: TagClass::Context,
663                inner: Vec::from(new_sup.as_bytes()),
664            }));
665        }
666        let req = Tag::Sequence(Sequence {
667            id: 12,
668            class: TagClass::Application,
669            inner: params,
670        });
671        Ok(self.op_call(LdapOp::Single, req).await?.0)
672    }
673
674    /// Perform an Extended operation given by `exop`. Extended operations are defined in the
675    /// [`exop`](exop/index.html) module. See the module-level documentation for the list of extended
676    /// operations supported by this library and procedures for defining custom exops.
677    pub async fn extended<E>(&mut self, exop: E) -> Result<ExopResult>
678    where
679        E: Into<Exop>,
680    {
681        let req = Tag::Sequence(Sequence {
682            id: 23,
683            class: TagClass::Application,
684            inner: construct_exop(exop.into()),
685        });
686        self.op_call(LdapOp::Single, req)
687            .await
688            .map(|et| ExopResult(et.1, et.0))
689    }
690
691    /// Terminate the connection to the server.
692    pub async fn unbind(&mut self) -> Result<()> {
693        let req = Tag::Null(Null {
694            id: 2,
695            class: TagClass::Application,
696            inner: (),
697        });
698        Ok(self.op_call(LdapOp::Unbind, req).await.map(|_| ())?)
699    }
700
701    /// Return the message ID of the last active operation. When the handle is initialized, this
702    /// value is set to zero. The intended use is to obtain the ID of a timed out operation for
703    /// passing it to an Abandon or Cancel operation.
704    ///
705    /// Using this method in the `start()` adapter chain of a streaming Search will return zero,
706    /// since the Message ID is obtained in the inner `start()` method.
707    pub fn last_id(&mut self) -> RequestId {
708        self.last_id
709    }
710
711    /// Ask the server to abandon an operation identified by `msgid`.
712    pub async fn abandon(&mut self, msgid: RequestId) -> Result<()> {
713        let req = Tag::Integer(Integer {
714            id: 16,
715            class: TagClass::Application,
716            inner: msgid as i64,
717        });
718        Ok(self
719            .op_call(LdapOp::Abandon(msgid), req)
720            .await
721            .map(|_| ())?)
722    }
723
724    /// Check whether the underlying connection has been closed.
725    ///
726    /// This is an indirect check: it queries the status of the channel for communicating with
727    /// the connection structure, not the connection socket itself. The channel being open
728    /// does not mean there is bidirecional communication with the server; to check for that,
729    /// a round-trip operation (e.g., `WhoAmI`) would be necessary.
730    pub fn is_closed(&mut self) -> bool {
731        self.tx.is_closed()
732    }
733
734    /// Return the TLS peer certificate in DER format.
735    ///
736    /// The method returns Ok(None) if no certificate was found or
737    /// the connection does not use or support TLS.
738    pub async fn get_peer_certificate(&mut self) -> Result<Option<Vec<u8>>> {
739        #[cfg(any(feature = "tls-native", feature = "tls-rustls"))]
740        {
741            let (tx, rx) = oneshot::channel();
742            self.misc_tx.send(MiscSender::Cert(tx))?;
743            Ok(rx.await?)
744        }
745        #[cfg(not(any(feature = "tls-native", feature = "tls-rustls")))]
746        {
747            Ok(None)
748        }
749    }
750}