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}