use crate::users::Users; use miette::{IntoDiagnostic, WrapErr}; use rsasl::callback::{CallbackError, Context, Request, SessionCallback, SessionData}; use rsasl::mechanism::SessionError; use rsasl::prelude::{Mechname, SASLConfig, SASLServer, Session, Validation}; use rsasl::property::{AuthId, AuthzId, Password}; use rsasl::validate::{Validate, ValidationError}; use std::sync::Arc; use crate::authentication::fabfire::FabFireCardKey; use crate::users::db::User; mod fabfire; mod fabfire_bin; struct Callback { users: Users, span: tracing::Span, } impl Callback { pub fn new(users: Users) -> Self { let span = tracing::info_span!("SASL callback"); Self { users, span } } } impl SessionCallback for Callback { fn callback( &self, _session_data: &SessionData, context: &Context, request: &mut Request, ) -> Result<(), SessionError> { if let Some(authid) = context.get_ref::() { request.satisfy_with::(|| { let user = self.users.get_user(authid).ok_or(CallbackError::NoValue)?; let kv = user .userdata .kv .get("cardkey") .ok_or(CallbackError::NoValue)?; let card_key = <[u8; 16]>::try_from(hex::decode(kv).map_err(|_| CallbackError::NoValue)?) .map_err(|_| CallbackError::NoValue)?; Ok(card_key) })?; } Ok(()) } fn validate( &self, session_data: &SessionData, context: &Context, validate: &mut Validate<'_>, ) -> Result<(), ValidationError> { let span = tracing::info_span!(parent: &self.span, "validate"); let _guard = span.enter(); if validate.is::() { match session_data.mechanism().mechanism.as_str() { "PLAIN" => { let authcid = context .get_ref::() .ok_or(ValidationError::MissingRequiredProperty)?; let authzid = context .get_ref::() .ok_or(ValidationError::MissingRequiredProperty)?; let password = context .get_ref::() .ok_or(ValidationError::MissingRequiredProperty)?; if !authzid.is_empty() { return Ok(()); } if let Some(user) = self.users.get_user(authcid) { match user.check_password(password) { Ok(true) => validate.finalize::(user), Ok(false) => { tracing::warn!(authid=%authcid, "AUTH FAILED: bad password"); } Err(error) => { tracing::warn!(authid=%authcid, "Bad DB entry: {}", error); } } } else { tracing::warn!(authid=%authcid, "AUTH FAILED: no such user"); } } "X-FABFIRE" | "X-FABFIRE-BIN" => { let authcid = context .get_ref::() .ok_or(ValidationError::MissingRequiredProperty)?; if let Some(user) = self.users.get_user(authcid) { validate.finalize::(user) } } _ => {} } } Ok(()) } } pub struct V; impl Validation for V { type Value = User; } #[derive(Clone)] struct Inner { rsasl: Arc, } impl Inner { pub fn new(rsasl: Arc) -> Self { Self { rsasl } } } #[derive(Clone)] pub struct AuthenticationHandle { inner: Inner, } impl AuthenticationHandle { pub fn new(userdb: Users) -> Self { let span = tracing::debug_span!("authentication"); let _guard = span.enter(); let config = SASLConfig::builder() .with_defaults() .with_callback(Callback::new(userdb)) .unwrap(); let mechs: Vec<&'static str> = SASLServer::::new(config.clone()) .get_available() .into_iter() .map(|m| m.mechanism.as_str()) .collect(); tracing::info!(available_mechs = mechs.len(), "initialized sasl backend"); tracing::debug!(?mechs, "available mechs"); Self { inner: Inner::new(config), } } pub fn start(&self, mechanism: &Mechname) -> miette::Result> { Ok(SASLServer::new(self.inner.rsasl.clone()) .start_suggested(mechanism) .into_diagnostic() .wrap_err("Failed to start a SASL authentication with the given mechanism")?) } pub fn sess(&self) -> SASLServer { SASLServer::new(self.inner.rsasl.clone()) } }