zookeeper_client/sasl/
gssapi.rs

1use std::borrow::Cow;
2
3use rsasl::callback::{Context, Request, SessionCallback, SessionData};
4use rsasl::mechanisms::gssapi::properties::GssService;
5use rsasl::prelude::*;
6use rsasl::property::Hostname;
7
8use super::{Result, SaslInitiator, SaslInnerOptions, SaslOptions, SaslSession};
9
10impl From<GssapiSaslOptions> for SaslOptions {
11    fn from(options: GssapiSaslOptions) -> Self {
12        Self(SaslInnerOptions::Gssapi(options))
13    }
14}
15
16/// GSSAPI SASL options.
17///
18/// Uses [SaslOptions::gssapi] to construct one.
19#[derive(Clone, Debug)]
20pub struct GssapiSaslOptions {
21    username: Cow<'static, str>,
22    hostname: Option<Cow<'static, str>>,
23}
24
25impl GssapiSaslOptions {
26    pub(crate) fn new() -> Self {
27        Self { username: Cow::from("zookeeper"), hostname: None }
28    }
29
30    /// Specifies the primary part of Kerberos principal.
31    ///
32    /// It is `zookeeper.sasl.client.username` in Java client, but the word "client" is misleading
33    /// as it is the username of targeting server.
34    ///
35    /// Defaults to "zookeeper".
36    pub fn with_username(self, username: impl Into<Cow<'static, str>>) -> Self {
37        Self { username: username.into(), ..self }
38    }
39
40    /// Specifies the instance part of Kerberos principal.
41    ///
42    /// Defaults to hostname or ip of targeting server in connecting string.
43    pub fn with_hostname(self, hostname: impl Into<Cow<'static, str>>) -> Self {
44        Self { hostname: Some(hostname.into()), ..self }
45    }
46
47    fn hostname_or(&self, hostname: &str) -> Cow<'static, str> {
48        match self.hostname.as_ref() {
49            None => Cow::Owned(hostname.to_string()),
50            Some(hostname) => hostname.clone(),
51        }
52    }
53}
54
55impl SaslInitiator for GssapiSaslOptions {
56    fn new_session(&self, hostname: &str) -> Result<SaslSession> {
57        struct GssapiOptionsProvider {
58            username: Cow<'static, str>,
59            hostname: Cow<'static, str>,
60        }
61        impl SessionCallback for GssapiOptionsProvider {
62            fn callback(
63                &self,
64                _session_data: &SessionData,
65                _context: &Context,
66                request: &mut Request<'_>,
67            ) -> Result<(), SessionError> {
68                if request.is::<Hostname>() {
69                    request.satisfy::<Hostname>(&self.hostname)?;
70                } else if request.is::<GssService>() {
71                    request.satisfy::<GssService>(&self.username)?;
72                }
73                Ok(())
74            }
75        }
76        let provider = GssapiOptionsProvider { username: self.username.clone(), hostname: self.hostname_or(hostname) };
77        let config = SASLConfig::builder().with_defaults().with_callback(provider).unwrap();
78        let client = SASLClient::new(config);
79        let session = client.start_suggested(&[Mechname::parse(b"GSSAPI").unwrap()]).unwrap();
80        SaslSession::new(session)
81    }
82}