zookeeper_client/sasl/
mod.rs

1#[allow(unused_imports)]
2use std::borrow::Cow;
3
4#[cfg(feature = "sasl-gssapi")]
5mod gssapi;
6#[cfg(feature = "sasl-gssapi")]
7pub use gssapi::*;
8
9#[cfg(feature = "sasl-digest-md5")]
10mod digest_md5;
11#[cfg(feature = "sasl-digest-md5")]
12pub use digest_md5::*;
13
14#[cfg(feature = "sasl-digest-md5")]
15mod mechanisms {
16    mod digest_md5;
17}
18
19use rsasl::prelude::*;
20
21use crate::error::Error;
22
23pub(crate) type Result<T, E = crate::error::Error> = std::result::Result<T, E>;
24
25pub(crate) trait SaslInitiator {
26    fn new_session(&self, hostname: &str) -> Result<SaslSession>;
27}
28
29/// Client side SASL options.
30#[derive(Clone, Debug)]
31pub struct SaslOptions(SaslInnerOptions);
32
33#[derive(Clone, Debug)]
34enum SaslInnerOptions {
35    #[cfg(feature = "sasl-gssapi")]
36    Gssapi(GssapiSaslOptions),
37    #[cfg(feature = "sasl-digest-md5")]
38    DigestMd5(DigestMd5SaslOptions),
39}
40
41impl SaslOptions {
42    /// Constructs a default [GssapiSaslOptions] for further customization.
43    ///
44    /// Make sure localhost is granted by Kerberos KDC, unlike Java counterpart this library
45    /// provides no mean to grant ticket from KDC but simply utilizes whatever the ticket cache
46    /// have.
47    #[cfg(feature = "sasl-gssapi")]
48    #[cfg_attr(docsrs, doc(cfg(any(feature = "sasl", feature = "sasl-gssapi"))))]
49    pub fn gssapi() -> GssapiSaslOptions {
50        GssapiSaslOptions::new()
51    }
52
53    /// Construct a [DigestMd5SaslOptions] for further customization.
54    #[cfg(feature = "sasl-digest-md5")]
55    #[cfg_attr(docsrs, doc(cfg(any(feature = "sasl", feature = "sasl-digest-md5"))))]
56    pub fn digest_md5(
57        username: impl Into<Cow<'static, str>>,
58        password: impl Into<Cow<'static, str>>,
59    ) -> DigestMd5SaslOptions {
60        DigestMd5SaslOptions::new(username, password)
61    }
62}
63
64impl SaslInitiator for SaslOptions {
65    fn new_session(&self, hostname: &str) -> Result<SaslSession> {
66        match &self.0 {
67            #[cfg(feature = "sasl-digest-md5")]
68            SaslInnerOptions::DigestMd5(options) => options.new_session(hostname),
69            #[cfg(feature = "sasl-gssapi")]
70            SaslInnerOptions::Gssapi(options) => options.new_session(hostname),
71        }
72    }
73}
74
75pub struct SaslSession {
76    output: Vec<u8>,
77    session: Session,
78    finished: bool,
79}
80
81impl SaslSession {
82    fn new(session: Session) -> Result<Self> {
83        let mut session = Self { session, output: Default::default(), finished: false };
84        if session.session.are_we_first() {
85            session.step(Default::default())?;
86        }
87        Ok(session)
88    }
89
90    pub fn name(&self) -> &str {
91        self.session.get_mechname().as_str()
92    }
93
94    pub fn initial(&self) -> &[u8] {
95        &self.output
96    }
97
98    pub fn step(&mut self, challenge: &[u8]) -> Result<Option<&[u8]>> {
99        if self.finished {
100            return Err(Error::UnexpectedError(format!("SASL {} session already finished", self.name())));
101        }
102        self.output.clear();
103        match self.session.step(Some(challenge), &mut self.output).map_err(Error::other)? {
104            State::Running => Ok(Some(&self.output)),
105            State::Finished(MessageSent::Yes) => {
106                self.finished = true;
107                Ok(Some(&self.output))
108            },
109            State::Finished(MessageSent::No) => {
110                self.finished = true;
111                Ok(None)
112            },
113        }
114    }
115}