//! Privileged PAM module. use std::{ ffi::{CStr, OsStr}, os::unix::ffi::OsStrExt as _, }; use nonstick::{ErrorCode, ModuleClient, PamModule, pam_export}; use keycloak::KeycloakAPI; use crate::ext::CStrExt as _; pub mod ext; pub mod keycloak; pub const DEFAULT_CONFIGURATION_PATH: &CStr = c"/etc/keycloak-pam.json"; struct KeycloakPAM; pam_export!(KeycloakPAM); /// # Arguments /// /// - `config=`, optional, accepts a path to load json configuration from, defaults to [`DEFAULT_CONFIGURATION_PATH`] /// - `debug`, optional, flag, ignores the PAM policy about silence and prints regardless pub struct Args<'a> { config: &'a CStr, debug: bool, } impl<'a> Args<'a> { fn parse(args: &[&'a CStr]) -> Self { let mut parsed_args = Args { config: DEFAULT_CONFIGURATION_PATH, debug: false, }; for arg in args { if arg == &c"debug" { parsed_args.debug = true; } if let Some((b"config", path)) = arg.split_ch(b'=') { parsed_args.config = path; } } parsed_args } } impl PamModule for KeycloakPAM { fn authenticate( handle: &mut M, args: Vec<&CStr>, flags: nonstick::AuthnFlags, ) -> nonstick::Result<()> { fn osstr_to_str(p: &OsStr) -> nonstick::Result<&str> { p.to_str().ok_or(ErrorCode::AuthInfoUnavailable) } let args = Args::parse(&args); let username = handle.username(None)?; let username = osstr_to_str(&username)?; let password = handle.authtok(None)?; let password = osstr_to_str(&password)?; let no_silent_flag = (flags & nonstick::AuthnFlags::SILENT).is_empty(); let can_print = no_silent_flag || args.debug; let api_client = match KeycloakAPI::load_from_configuration(OsStr::from_bytes(args.config.to_bytes())) { Ok(v) => v, Err(error) => { if can_print { eprintln!("{error}"); } return Err(ErrorCode::ServiceError); } }; match api_client.check_credentials(username, password) { Ok(()) => Ok(()), Err(error) => { if can_print { eprintln!("{error}"); } let err = match error { keycloak::LoginError::Fetch(_) | keycloak::LoginError::InvalidAPIResponse(_) => ErrorCode::ServiceError, keycloak::LoginError::InvalidLogin(_) => ErrorCode::CredentialsError, }; Err(err) } } } // fn account_management( // handle: &mut M, // args: Vec<&std::ffi::CStr>, // flags: nonstick::AuthnFlags, // ) -> nonstick::Result<()> { // todo!() // } fn change_authtok( handle: &mut M, args: Vec<&std::ffi::CStr>, action: nonstick::AuthtokAction, flags: nonstick::AuthtokFlags, ) -> nonstick::Result<()> { _ = (handle, args, action, flags); todo!() } }