Mercurial > crates > nonstick
diff src/libpam/question.rs @ 80:5aa1a010f1e8
Start using PAM headers; improve owned/borrowed distinction.
- Uses bindgen to generate bindings (only if needed).
- Gets the story together on owned vs. borrowed handles.
- Reduces number of mutable borrows in handle operation
(since `PamHandle` is neither `Send` nor `Sync`,
we never have to worry about thread safety.
- Improves a bunch of macros so we don't have our own
special syntax for docs.
- Implement question indirection for standard XSSO PAM implementations.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Tue, 10 Jun 2025 01:09:30 -0400 |
parents | 2128123b9406 |
children | 5e14bb093851 |
line wrap: on
line diff
--- a/src/libpam/question.rs Sun Jun 08 04:21:58 2025 -0400 +++ b/src/libpam/question.rs Tue Jun 10 01:09:30 2025 -0400 @@ -1,16 +1,13 @@ //! Data and types dealing with PAM messages. -use crate::constants::InvalidEnum; use crate::conv::{BinaryQAndA, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA, RadioQAndA}; use crate::libpam::conversation::OwnedMessage; use crate::libpam::memory; use crate::libpam::memory::{CBinaryData, Immovable}; +pub use crate::libpam::pam_ffi::{Question, Style}; use crate::ErrorCode; use crate::Result; -use num_derive::FromPrimitive; -use num_traits::FromPrimitive; -use std::ffi::{c_int, c_void, CStr}; -use std::result::Result as StdResult; +use std::ffi::{c_void, CStr}; use std::{iter, ptr, slice}; /// Abstraction of a collection of questions to be sent in a PAM conversation. @@ -57,21 +54,19 @@ /// ╟──────────────╢ /// ║ ... ║ /// ``` -/// -/// ***THIS LIBRARY CURRENTLY SUPPORTS ONLY LINUX-PAM.*** -pub struct Questions { +pub struct GenericQuestions<I: IndirectTrait> { /// An indirection to the questions themselves, stored on the C heap. - indirect: *mut Indirect, + indirect: *mut I, /// The number of questions. count: usize, } -impl Questions { +impl<I: IndirectTrait> GenericQuestions<I> { /// Stores the provided questions on the C heap. pub fn new(messages: &[Message]) -> Result<Self> { let count = messages.len(); let mut ret = Self { - indirect: Indirect::alloc(count), + indirect: I::alloc(count), count, }; // Even if we fail partway through this, all our memory will be freed. @@ -82,8 +77,8 @@ } /// The pointer to the thing with the actual list. - pub fn indirect(&self) -> *const Indirect { - self.indirect + pub fn indirect(&self) -> *const *const Question { + self.indirect.cast() } pub fn iter(&self) -> impl Iterator<Item = &Question> { @@ -96,13 +91,13 @@ } } -impl Drop for Questions { +impl<I: IndirectTrait> Drop for GenericQuestions<I> { fn drop(&mut self) { // SAFETY: We are valid and have a valid pointer. // Once we're done, everything will be safe. unsafe { if let Some(indirect) = self.indirect.as_mut() { - indirect.free(self.count) + indirect.free_contents(self.count) } memory::free(self.indirect); self.indirect = ptr::null_mut(); @@ -110,56 +105,131 @@ } } +/// The trait that each of the `Indirect` implementations implement. +/// +/// Basically a slice but with more meat. +pub trait IndirectTrait { + /// Converts a pointer into a borrowed `Self`. + /// + /// # Safety + /// + /// You have to provide a valid pointer. + unsafe fn borrow_ptr<'a>(ptr: *const *const Question) -> Option<&'a Self> + where + Self: Sized, + { + ptr.cast::<Self>().as_ref() + } + + /// Allocates memory for this indirector and all its members. + fn alloc(count: usize) -> *mut Self; + + /// Returns an iterator yielding the given number of messages. + /// + /// # Safety + /// + /// You have to provide the right count. + unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &Question>; + + /// Returns a mutable iterator yielding the given number of messages. + /// + /// # Safety + /// + /// You have to provide the right count. + unsafe fn iter_mut(&mut self, count: usize) -> impl Iterator<Item = &mut Question>; + + /// Frees everything this points to. + /// + /// # Safety + /// + /// You have to pass the right size. + unsafe fn free_contents(&mut self, count: usize); +} + +/// An indirect reference to messages. +/// +/// This is kept separate to provide a place where we can separate +/// the pointer-to-pointer-to-list from pointer-to-list-of-pointers. +#[cfg(pam_impl = "linux-pam")] +pub type Indirect = LinuxPamIndirect; + /// An indirect reference to messages. /// /// This is kept separate to provide a place where we can separate /// the pointer-to-pointer-to-list from pointer-to-list-of-pointers. +#[cfg(not(pam_impl = "linux-pam"))] +pub type Indirect = XssoIndirect; + +pub type Questions = GenericQuestions<Indirect>; + +/// The XSSO standard version of the indirection layer between Question and Questions. #[repr(transparent)] -pub struct Indirect { +pub struct StandardIndirect { + base: *mut Question, + _marker: Immovable, +} + +impl IndirectTrait for StandardIndirect { + fn alloc(count: usize) -> *mut Self { + let questions = memory::calloc(count); + let me_ptr: *mut Self = memory::calloc(1); + // SAFETY: We just allocated this, and we're putting a valid pointer in. + unsafe { + let me = &mut *me_ptr; + me.base = questions; + } + me_ptr + } + + unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &Question> { + (0..count).map(|idx| &*self.base.add(idx)) + } + + unsafe fn iter_mut(&mut self, count: usize) -> impl Iterator<Item = &mut Question> { + (0..count).map(|idx| &mut *self.base.add(idx)) + } + + unsafe fn free_contents(&mut self, count: usize) { + let msgs = slice::from_raw_parts_mut(self.base, count); + for msg in msgs { + msg.clear() + } + memory::free(self.base); + self.base = ptr::null_mut() + } +} + +/// The Linux version of the indirection layer between Question and Questions. +#[repr(transparent)] +pub struct LinuxPamIndirect { base: [*mut Question; 0], _marker: Immovable, } -impl Indirect { - /// Allocates memory for this indirector and all its members. +impl IndirectTrait for LinuxPamIndirect { fn alloc(count: usize) -> *mut Self { // SAFETY: We're only allocating, and when we're done, // everything will be in a known-good state. - let me_ptr: *mut Indirect = memory::calloc::<Question>(count).cast(); + let me_ptr: *mut Self = memory::calloc::<*mut Question>(count).cast(); unsafe { let me = &mut *me_ptr; let ptr_list = slice::from_raw_parts_mut(me.base.as_mut_ptr(), count); for entry in ptr_list { *entry = memory::calloc(1); } - me } + me_ptr } - /// Returns an iterator yielding the given number of messages. - /// - /// # Safety - /// - /// You have to provide the right count. - pub unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &Question> { + unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &Question> { (0..count).map(|idx| &**self.base.as_ptr().add(idx)) } - /// Returns a mutable iterator yielding the given number of messages. - /// - /// # Safety - /// - /// You have to provide the right count. - pub unsafe fn iter_mut(&mut self, count: usize) -> impl Iterator<Item = &mut Question> { + unsafe fn iter_mut(&mut self, count: usize) -> impl Iterator<Item = &mut Question> { (0..count).map(|idx| &mut **self.base.as_mut_ptr().add(idx)) } - /// Frees everything this points to. - /// - /// # Safety - /// - /// You have to pass the right size. - unsafe fn free(&mut self, count: usize) { + unsafe fn free_contents(&mut self, count: usize) { let msgs = slice::from_raw_parts_mut(self.base.as_mut_ptr(), count); for msg in msgs { if let Some(msg) = msg.as_mut() { @@ -171,66 +241,23 @@ } } -/// The C enum values for messages shown to the user. -#[derive(Debug, PartialEq, FromPrimitive)] -pub enum Style { - /// Requests information from the user; will be masked when typing. - PromptEchoOff = 1, - /// Requests information from the user; will not be masked. - PromptEchoOn = 2, - /// An error message. - ErrorMsg = 3, - /// An informational message. - TextInfo = 4, - /// Yes/No/Maybe conditionals. A Linux-PAM extension. - RadioType = 5, - /// For server–client non-human interaction. - /// - /// NOT part of the X/Open PAM specification. - /// A Linux-PAM extension. - BinaryPrompt = 7, -} - -impl TryFrom<c_int> for Style { - type Error = InvalidEnum<Self>; - fn try_from(value: c_int) -> StdResult<Self, Self::Error> { - Self::from_i32(value).ok_or(value.into()) +impl Default for Question { + fn default() -> Self { + Self { + style: Default::default(), + data: ptr::null_mut(), + _marker: Default::default(), + } } } -impl From<Style> for c_int { - fn from(val: Style) -> Self { - val as Self - } -} - -/// A question sent by PAM or a module to an application. -/// -/// PAM refers to this as a "message", but we call it a question -/// to avoid confusion with [`Message`]. -/// -/// This question, and its internal data, is owned by its creator -/// (either the module or PAM itself). -#[repr(C)] -pub struct Question { - /// The style of message to request. - style: c_int, - /// A description of the data requested. - /// - /// For most requests, this will be an owned [`CStr`], but for requests - /// with [`Style::BinaryPrompt`], this will be [`CBinaryData`] - /// (a Linux-PAM extension). - data: *mut c_void, - _marker: Immovable, -} - impl Question { /// Replaces the contents of this question with the question /// from the message. pub fn fill(&mut self, msg: &Message) -> Result<()> { let (style, data) = copy_to_heap(msg)?; self.clear(); - self.style = style as c_int; + self.style = style.into(); self.data = data; Ok(()) } @@ -327,45 +354,52 @@ #[cfg(test)] mod tests { - use super::{MaskedQAndA, Questions, Result}; - use crate::conv::{BinaryQAndA, ErrorMsg, InfoMsg, QAndA, RadioQAndA}; - use crate::libpam::conversation::OwnedMessage; + use super::{ + BinaryQAndA, ErrorMsg, GenericQuestions, IndirectTrait, InfoMsg, LinuxPamIndirect, + MaskedQAndA, OwnedMessage, QAndA, RadioQAndA, Result, StandardIndirect, + }; - #[test] - fn test_round_trip() { - let interrogation = Questions::new(&[ - MaskedQAndA::new("hocus pocus").message(), - BinaryQAndA::new((&[5, 4, 3, 2, 1], 66)).message(), - QAndA::new("what").message(), - QAndA::new("who").message(), - InfoMsg::new("hey").message(), - ErrorMsg::new("gasp").message(), - RadioQAndA::new("you must choose").message(), - ]) - .unwrap(); - let indirect = interrogation.indirect(); + macro_rules! assert_matches { + ($id:ident => $variant:path, $q:expr) => { + if let $variant($id) = $id { + assert_eq!($q, $id.question()); + } else { + panic!("mismatched enum variant {x:?}", x = $id); + } + }; + } - let remade = unsafe { indirect.as_ref() }.unwrap(); - let messages: Vec<OwnedMessage> = unsafe { remade.iter(interrogation.count) } - .map(TryInto::try_into) - .collect::<Result<_>>() + macro_rules! tests { ($fn_name:ident<$typ:ident>) => { + #[test] + fn $fn_name() { + let interrogation = GenericQuestions::<$typ>::new(&[ + MaskedQAndA::new("hocus pocus").message(), + BinaryQAndA::new((&[5, 4, 3, 2, 1], 66)).message(), + QAndA::new("what").message(), + QAndA::new("who").message(), + InfoMsg::new("hey").message(), + ErrorMsg::new("gasp").message(), + RadioQAndA::new("you must choose").message(), + ]) .unwrap(); - let [masked, bin, what, who, hey, gasp, choose] = messages.try_into().unwrap(); - macro_rules! assert_matches { - ($id:ident => $variant:path, $q:expr) => { - if let $variant($id) = $id { - assert_eq!($q, $id.question()); - } else { - panic!("mismatched enum variant {x:?}", x = $id); - } - }; + let indirect = interrogation.indirect(); + + let remade = unsafe { $typ::borrow_ptr(indirect) }.unwrap(); + let messages: Vec<OwnedMessage> = unsafe { remade.iter(interrogation.count) } + .map(TryInto::try_into) + .collect::<Result<_>>() + .unwrap(); + let [masked, bin, what, who, hey, gasp, choose] = messages.try_into().unwrap(); + assert_matches!(masked => OwnedMessage::MaskedPrompt, "hocus pocus"); + assert_matches!(bin => OwnedMessage::BinaryPrompt, (&[5, 4, 3, 2, 1][..], 66)); + assert_matches!(what => OwnedMessage::Prompt, "what"); + assert_matches!(who => OwnedMessage::Prompt, "who"); + assert_matches!(hey => OwnedMessage::Info, "hey"); + assert_matches!(gasp => OwnedMessage::Error, "gasp"); + assert_matches!(choose => OwnedMessage::RadioPrompt, "you must choose"); } - assert_matches!(masked => OwnedMessage::MaskedPrompt, "hocus pocus"); - assert_matches!(bin => OwnedMessage::BinaryPrompt, (&[5, 4, 3, 2, 1][..], 66)); - assert_matches!(what => OwnedMessage::Prompt, "what"); - assert_matches!(who => OwnedMessage::Prompt, "who"); - assert_matches!(hey => OwnedMessage::Info, "hey"); - assert_matches!(gasp => OwnedMessage::Error, "gasp"); - assert_matches!(choose => OwnedMessage::RadioPrompt, "you must choose"); - } + }} + + tests!(test_xsso<StandardIndirect>); + tests!(test_linux<LinuxPamIndirect>); }