use crate::{
client::auth::{LoadSecret, Sasl, SaslLogic, Secret},
string::{Arg, NoNul, SecretBuf},
};
use std::collections::BTreeSet;
static SASL_PLAIN_NAME: Arg = Arg::from_str("PLAIN");
#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde_derive::Deserialize))]
pub enum PasswordMechanism {
#[default]
Plain,
}
impl PasswordMechanism {
pub(self) fn full_set() -> BTreeSet<PasswordMechanism> {
[PasswordMechanism::Plain].into_iter().collect()
}
pub(self) fn logic(&self, authzid: &[u8], authcid: &[u8], passwd: &[u8]) -> Box<dyn SaslLogic> {
match self {
PasswordMechanism::Plain => Box::new(PlainLogic::new(authzid, authcid, passwd)),
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde_derive::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(bound(deserialize = "S: LoadSecret + serde::de::Deserialize<'de>"))
)]
pub struct Password<S> {
#[cfg_attr(feature = "serde", serde(default))]
pub deny_methods: BTreeSet<PasswordMechanism>,
#[cfg_attr(feature = "serde", serde(default))]
pub authzid: NoNul<'static>,
pub authcid: NoNul<'static>,
pub passwd: Secret<NoNul<'static>, S>,
}
impl<S> Password<S> {
pub const fn new(username: NoNul<'static>, passwd: Secret<NoNul<'static>, S>) -> Self {
Password {
deny_methods: BTreeSet::new(),
authzid: NoNul::empty(),
authcid: username,
passwd,
}
}
}
impl<S> Sasl for Password<S> {
fn logic(&self) -> Vec<Box<dyn SaslLogic>> {
PasswordMechanism::full_set()
.difference(&self.deny_methods)
.copied()
.map(|mech| mech.logic(&self.authzid, &self.authcid, &self.passwd))
.collect()
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde_derive::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(bound(deserialize = "'de: 'static, S: LoadSecret + serde::de::Deserialize<'de>"))
)]
pub struct Plain<S> {
#[cfg_attr(feature = "serde", serde(default))]
pub authzid: NoNul<'static>,
pub authcid: NoNul<'static>,
pub passwd: Secret<NoNul<'static>, S>,
}
impl<S> Plain<S> {
pub const fn new(username: NoNul<'static>, passwd: Secret<NoNul<'static>, S>) -> Self {
Plain { authzid: NoNul::empty(), authcid: username, passwd }
}
}
impl<S: LoadSecret + 'static> Sasl for Plain<S> {
fn logic(&self) -> Vec<Box<dyn SaslLogic>> {
let authzid = self.authzid.as_bytes();
let authcid = self.authcid.as_bytes();
let passwd = self.passwd.as_bytes();
vec![Box::new(PlainLogic::new(authzid, authcid, passwd))]
}
}
struct PlainLogic(SecretBuf);
impl PlainLogic {
pub fn new(authzid: &[u8], authcid: &[u8], passwd: &[u8]) -> Self {
let mut data = SecretBuf::with_capacity(2 + passwd.len() + authcid.len() + authzid.len());
data.push_cstr(authzid);
data.push_cstr(authcid);
data.push_slice(passwd);
PlainLogic(data)
}
}
impl SaslLogic for PlainLogic {
fn name(&self) -> Arg<'static> {
SASL_PLAIN_NAME.clone()
}
fn reply<'a>(
&'a mut self,
data: &[u8],
output: &mut SecretBuf,
) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
if !data.is_empty() {
return Err("non-empty server message".into());
}
if self.0.is_empty() {
return Err("already sent auth".into());
}
std::mem::swap(output, &mut self.0);
Ok(())
}
}