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#[allow(dead_code)]
29#[derive(Clone, Debug)]
30pub(crate) struct SaslCreds(pub Option<Vec<u8>>);
31
32#[derive(Clone, Debug, PartialEq)]
34pub enum Mod<S: AsRef<[u8]> + Eq + Hash> {
35 Add(S, HashSet<S>),
37 Delete(S, HashSet<S>),
39 Replace(S, HashSet<S>),
41 Increment(S, S),
43}
44
45#[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)>>, #[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 pub fn with_search_options(&mut self, opts: SearchOptions) -> &mut Self {
209 self.search_opts = Some(opts);
210 self
211 }
212
213 pub fn with_controls<V: IntoRawControlVec>(&mut self, ctrls: V) -> &mut Self {
227 self.controls = Some(ctrls.into());
228 self
229 }
230
231 pub fn with_timeout(&mut self, duration: Duration) -> &mut Self {
240 self.timeout = Some(duration);
241 self
242 }
243
244 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn last_id(&mut self) -> RequestId {
840 self.last_id
841 }
842
843 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 pub fn is_closed(&mut self) -> bool {
860 self.tx.is_closed()
861 }
862
863 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}