Mercurial > crates > nonstick
changeset 97:efe2f5f8b5b2
Implement "stateless" application-side PAM calls.
This introduces `authenticate`, `account_management`, and `change_authtok`.
These are the three PAM operations that are stateless (i.e., they don't start
a session or modify global credentials).
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Mon, 23 Jun 2025 19:10:34 -0400 |
parents | f3e260f9ddcb |
children | b87100c5eed4 |
files | build.rs src/constants.rs src/handle.rs src/libpam/conversation.rs src/libpam/handle.rs src/libpam/pam_ffi.rs |
diffstat | 6 files changed, 90 insertions(+), 10 deletions(-) [+] |
line wrap: on
line diff
--- a/build.rs Mon Jun 23 14:26:34 2025 -0400 +++ b/build.rs Mon Jun 23 19:10:34 2025 -0400 @@ -18,6 +18,9 @@ .allowlist_function("pam_get_authtok") .allowlist_function("pam_end") .allowlist_function("pam_strerror") + .allowlist_function("pam_authenticate") + .allowlist_function("pam_chauthtok") + .allowlist_function("pam_acct_mgmt") .default_macro_constant_type(MacroTypeVariation::Unsigned); let linux_builder = common_builder
--- a/src/constants.rs Mon Jun 23 14:26:34 2025 -0400 +++ b/src/constants.rs Mon Jun 23 19:10:34 2025 -0400 @@ -95,7 +95,7 @@ /// /// See `/usr/include/security/_pam_types.h` and /// See `/usr/include/security/pam_modules.h` for more details. - #[derive(Debug, PartialEq)] + #[derive(Debug, Default, PartialEq)] #[repr(transparent)] pub struct Flags: c_uint { /// The module should not generate any messages.
--- a/src/handle.rs Mon Jun 23 14:26:34 2025 -0400 +++ b/src/handle.rs Mon Jun 23 19:10:34 2025 -0400 @@ -1,6 +1,6 @@ //! The wrapper types and traits for handles into the PAM library. -use crate::constants::Result; +use crate::constants::{Flags, Result}; use crate::conv::Conversation; use crate::logging::Level; @@ -248,7 +248,14 @@ /// Like [`PamShared`], this is intended to allow creating mock implementations /// of PAM for testing PAM applications. pub trait PamHandleApplication: PamShared { - // reserved! + /// Starts the authentication process for the user. + fn authenticate(&mut self, flags: Flags) -> Result<()>; + + /// Does "account management". + fn account_management(&mut self, flags: Flags) -> Result<()>; + + /// Changes the authentication token. + fn change_authtok(&mut self, flags: Flags) -> Result<()>; } /// Functionality of a PAM handle that can be expected by a PAM module.
--- a/src/libpam/conversation.rs Mon Jun 23 14:26:34 2025 -0400 +++ b/src/libpam/conversation.rs Mon Jun 23 19:10:34 2025 -0400 @@ -15,10 +15,10 @@ use std::result::Result as StdResult; impl LibPamConversation<'_> { - fn wrap<C: Conversation>(conv: &mut C) -> Self { + pub fn wrap<C: Conversation>(conv: &C) -> Self { Self { callback: Self::wrapper_callback::<C>, - appdata: (conv as *mut C).cast(), + appdata: (conv as *const C).cast(), life: PhantomData, _marker: Immovable(PhantomData), } @@ -31,13 +31,13 @@ count: c_int, questions: *const *const Question, answers: *mut *mut Answer, - me: *mut AppData, + me: *const AppData, ) -> c_int { let internal = || { // Collect all our pointers let conv = me .cast::<C>() - .as_mut() + .as_ref() .ok_or(ErrorCode::ConversationError)?; let indirect = Questions::borrow_ptr(questions, count as usize); let answers_ptr = answers.as_mut().ok_or(ErrorCode::ConversationError)?;
--- a/src/libpam/handle.rs Mon Jun 23 14:26:34 2025 -0400 +++ b/src/libpam/handle.rs Mon Jun 23 19:10:34 2025 -0400 @@ -5,7 +5,7 @@ pub use crate::libpam::pam_ffi::LibPamHandle; use crate::libpam::{memory, pam_ffi}; use crate::logging::Level; -use crate::{Conversation, PamHandleModule}; +use crate::{Conversation, Flags, PamHandleApplication, PamHandleModule}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::cell::Cell; use std::ffi::{c_char, c_int, CString}; @@ -36,6 +36,76 @@ _conversation_lifetime: PhantomData<&'a mut ()>, } +#[derive(Debug, PartialEq)] +pub struct HandleBuilder { + service_name: String, + username: Option<String>, +} + +impl HandleBuilder { + /// Creates a new HandleBuilder for the given service. + fn new(service_name: String) -> Self { + Self{service_name, username: Default::default()} + } + /// Updates the service name. + pub fn service_name(mut self, service_name: String) -> Self { + self.service_name = service_name; self + } + /// Updates the username. + pub fn username(mut self, username: String) -> Self { + self.username = Some(username); self + } + + pub fn build(self, conv: &impl Conversation) -> Result<OwnedLibPamHandle> { + OwnedLibPamHandle::start(self.service_name, self.username, conv) + } +} + +impl OwnedLibPamHandle<'_> { + pub fn build_with_service(service_name: String) -> HandleBuilder { + HandleBuilder::new(service_name) + } + fn start(service_name: String, username: Option<String>, conversation: &impl Conversation) -> Result<Self> { + let conv = LibPamConversation::wrap(conversation); + let service_cstr = CString::new(service_name).map_err(|_| ErrorCode::ConversationError)?; + let username_cstr = memory::prompt_ptr(memory::option_cstr(username.as_deref())?.as_ref()); + + let mut handle: *mut LibPamHandle = ptr::null_mut(); + // SAFETY: We've set everything up properly to call `pam_start`. + // The returned value will be a valid pointer provided the result is OK. + let result = unsafe { pam_ffi::pam_start(service_cstr.as_ptr(), username_cstr, &conv, &mut handle) }; + ErrorCode::result_from(result)?; + Ok(Self{ + handle: HandleWrap(handle), + last_return: Cell::new(Ok(())), + _conversation_lifetime: Default::default(), + }) + } +} + +impl PamHandleApplication for OwnedLibPamHandle<'_> { + fn authenticate(&mut self, flags: Flags) -> Result<()> { + let ret = unsafe { pam_ffi::pam_authenticate(self.handle.0, flags.bits() as c_int)}; + let result = ErrorCode::result_from(ret); + self.last_return.set(result); + result + } + + fn account_management(&mut self, flags: Flags) -> Result<()> { + let ret = unsafe { pam_ffi::pam_acct_mgmt(self.handle.0, flags.bits() as c_int)}; + let result = ErrorCode::result_from(ret); + self.last_return.set(result); + result + } + + fn change_authtok(&mut self, flags: Flags) -> Result<()> { + let ret = unsafe { pam_ffi::pam_chauthtok(self.handle.0, flags.bits() as c_int)}; + let result = ErrorCode::result_from(ret); + self.last_return.set(result); + result + } +} + // TODO: pam_authenticate - app // pam_setcred - app // pam_acct_mgmt - app
--- a/src/libpam/pam_ffi.rs Mon Jun 23 14:26:34 2025 -0400 +++ b/src/libpam/pam_ffi.rs Mon Jun 23 19:10:34 2025 -0400 @@ -76,7 +76,7 @@ num_msg: c_int, questions: *const *const Question, answers: *mut *mut Answer, - appdata: *mut AppData, + appdata: *const AppData, ) -> c_int; /// The type used by PAM to call back into a conversation. @@ -86,7 +86,7 @@ pub callback: ConversationCallback, /// The pointer that will be passed as the last parameter /// to the conversation callback. - pub appdata: *mut AppData, + pub appdata: *const AppData, pub life: PhantomData<&'a mut ()>, pub _marker: Immovable, }