Mercurial > crates > nonstick
diff src/pam_ffi/message.rs @ 73:ac6881304c78
Do conversations, along with way too much stuff.
This implements conversations, along with all the memory management
brouhaha that goes along with it. The conversation now lives directly
on the handle rather than being a thing you have to get from it
and then call manually. It Turns Out this makes things a lot easier!
I guess we reorganized things again. For the last time. For real.
I promise.
This all passes ASAN, so it seems Pretty Good!
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Thu, 05 Jun 2025 03:41:38 -0400 |
parents | 47eb242a4f88 |
children | c7c596e6388f |
line wrap: on
line diff
--- a/src/pam_ffi/message.rs Wed Jun 04 03:53:36 2025 -0400 +++ b/src/pam_ffi/message.rs Thu Jun 05 03:41:38 2025 -0400 @@ -1,77 +1,19 @@ //! Data and types dealing with PAM messages. use crate::constants::InvalidEnum; +use crate::conv::Message; use crate::pam_ffi::memory; -use crate::pam_ffi::memory::{CBinaryData, NulError, TooBigError}; +use crate::pam_ffi::memory::{CBinaryData, Immovable, NulError, TooBigError}; use num_derive::FromPrimitive; use num_traits::FromPrimitive; -use std::ffi::{c_char, c_int, c_void, CStr}; +use std::ffi::{c_int, c_void, CStr}; use std::result::Result as StdResult; -use std::slice; use std::str::Utf8Error; - -/// The types of message and request that can be sent to a user. -/// -/// The data within each enum value is the prompt (or other information) -/// that will be presented to the user. -#[derive(Debug)] -pub enum Message<'a> { - /// Requests information from the user; will be masked when typing. - /// - /// Response: [`MaskedText`](crate::conv::Response::MaskedText) - MaskedPrompt(&'a str), - /// Requests information from the user; will not be masked. - /// - /// Response: [`Text`](crate::conv::Response::Text) - Prompt(&'a str), - /// "Yes/No/Maybe conditionals" (a Linux-PAM extension). - /// - /// Response: [`Text`](crate::conv::Response::Text) - /// (Linux-PAM documentation doesn't define its contents.) - RadioPrompt(&'a str), - /// Raises an error message to the user. - /// - /// Response: [`NoResponse`](crate::conv::Response::NoResponse) - Error(&'a str), - /// Sends an informational message to the user. - /// - /// Response: [`NoResponse`](crate::conv::Response::NoResponse) - Info(&'a str), - /// Requests binary data from the client (a Linux-PAM extension). - /// - /// This is used for non-human or non-keyboard prompts (security key?). - /// NOT part of the X/Open PAM specification. - /// - /// Response: [`Binary`](crate::conv::Response::Binary) - BinaryPrompt { - /// Some binary data. - data: &'a [u8], - /// A "type" that you can use for signalling. Has no strict definition in PAM. - data_type: u8, - }, -} - -impl Message<'_> { - /// Copies the contents of this message to the C heap. - fn copy_to_heap(&self) -> StdResult<(Style, *mut c_void), ConversionError> { - let alloc = |style, text| Ok((style, memory::malloc_str(text)?.cast())); - match *self { - Self::MaskedPrompt(text) => alloc(Style::PromptEchoOff, text), - Self::Prompt(text) => alloc(Style::PromptEchoOn, text), - Self::RadioPrompt(text) => alloc(Style::RadioType, text), - Self::Error(text) => alloc(Style::ErrorMsg, text), - Self::Info(text) => alloc(Style::TextInfo, text), - Self::BinaryPrompt { data, data_type } => Ok(( - Style::BinaryPrompt, - (CBinaryData::alloc(data, data_type)?).cast(), - )), - } - } -} +use std::{ptr, slice}; #[derive(Debug, thiserror::Error)] #[error("error creating PAM message: {0}")] -enum ConversionError { +pub enum ConversionError { InvalidEnum(#[from] InvalidEnum<Style>), Utf8Error(#[from] Utf8Error), NulError(#[from] NulError), @@ -121,14 +63,15 @@ /// A description of the data requested. /// /// For most requests, this will be an owned [`CStr`], but for requests - /// with [`Style::BinaryPrompt`], this will be [`BinaryData`] + /// with [`Style::BinaryPrompt`], this will be [`CBinaryData`] /// (a Linux-PAM extension). data: *mut c_void, + _marker: Immovable, } impl RawMessage { - fn set(&mut self, msg: &Message) -> StdResult<(), ConversionError> { - let (style, data) = msg.copy_to_heap()?; + pub fn set(&mut self, msg: Message) -> StdResult<(), ConversionError> { + let (style, data) = copy_to_heap(msg)?; self.clear(); // SAFETY: We allocated this ourselves or were given it by PAM. // Otherwise, it's null, but free(null) is fine. @@ -138,32 +81,6 @@ Ok(()) } - /// Retrieves the data stored in this message. - fn data(&self) -> StdResult<Message, ConversionError> { - let style: Style = self.style.try_into()?; - // SAFETY: We either allocated this message ourselves or were provided it by PAM. - let result = unsafe { - match style { - Style::PromptEchoOff => Message::MaskedPrompt(self.string_data()?), - Style::PromptEchoOn => Message::Prompt(self.string_data()?), - Style::TextInfo => Message::Info(self.string_data()?), - Style::ErrorMsg => Message::Error(self.string_data()?), - Style::RadioType => Message::Error(self.string_data()?), - Style::BinaryPrompt => (self.data as *const CBinaryData).as_ref().map_or_else( - || Message::BinaryPrompt { - data_type: 0, - data: &[], - }, - |data| Message::BinaryPrompt { - data_type: data.data_type(), - data: data.contents(), - }, - ), - } - }; - Ok(result) - } - /// Gets this message's data pointer as a string. /// /// # Safety @@ -173,7 +90,7 @@ if self.data.is_null() { Ok("") } else { - CStr::from_ptr(self.data as *const c_char).to_str() + CStr::from_ptr(self.data.cast()).to_str() } } @@ -185,7 +102,7 @@ if let Ok(style) = Style::try_from(self.style) { match style { Style::BinaryPrompt => { - if let Some(d) = (self.data as *mut CBinaryData).as_mut() { + if let Some(d) = self.data.cast::<CBinaryData>().as_mut() { d.zero_contents() } } @@ -196,10 +113,28 @@ | Style::PromptEchoOn => memory::zero_c_string(self.data), } }; + libc::free(self.data); + self.data = ptr::null_mut(); } } } +/// Copies the contents of this message to the C heap. +fn copy_to_heap(msg: Message) -> StdResult<(Style, *mut c_void), ConversionError> { + let alloc = |style, text| Ok((style, memory::malloc_str(text)?.cast())); + match msg { + Message::MaskedPrompt(text) => alloc(Style::PromptEchoOff, text), + Message::Prompt(text) => alloc(Style::PromptEchoOn, text), + Message::RadioPrompt(text) => alloc(Style::RadioType, text), + Message::Error(text) => alloc(Style::ErrorMsg, text), + Message::Info(text) => alloc(Style::TextInfo, text), + Message::BinaryPrompt { data, data_type } => Ok(( + Style::BinaryPrompt, + (CBinaryData::alloc(data, data_type)?).cast(), + )), + } +} + /// Abstraction of a list-of-messages to be sent in a PAM conversation. /// /// On Linux-PAM and other compatible implementations, `messages` @@ -235,10 +170,9 @@ /// ``` /// /// ***THIS LIBRARY CURRENTLY SUPPORTS ONLY LINUX-PAM.*** -#[repr(C)] pub struct OwnedMessages { /// An indirection to the messages themselves, stored on the C heap. - indirect: *mut Indirect<RawMessage>, + indirect: *mut MessageIndirector, /// The number of messages in the list. count: usize, } @@ -246,47 +180,155 @@ impl OwnedMessages { /// Allocates data to store messages on the C heap. pub fn alloc(count: usize) -> Self { - // SAFETY: We're allocating. That's safe. + Self { + indirect: MessageIndirector::alloc(count), + count, + } + } + + /// The pointer to the thing with the actual list. + pub fn indirector(&self) -> *const MessageIndirector { + self.indirect + } + + pub fn iter(&self) -> impl Iterator<Item = &RawMessage> { + // SAFETY: we're iterating over an amount we know. + unsafe { (*self.indirect).iter(self.count) } + } + + pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut RawMessage> { + // SAFETY: we're iterating over an amount we know. + unsafe { (*self.indirect).iter_mut(self.count) } + } +} + +impl Drop for OwnedMessages { + fn drop(&mut self) { + // SAFETY: We are valid and have a valid pointer. + // Once we're done, everything will be safe. unsafe { - // Since this is Linux-PAM, the indirect is a list of pointers. - let indirect = - libc::calloc(count, size_of::<Indirect<RawMessage>>()) as *mut Indirect<RawMessage>; - let indir_ptrs = slice::from_raw_parts_mut(indirect, count); - for ptr in indir_ptrs { - ptr.base = libc::calloc(1, size_of::<RawMessage>()) as *mut RawMessage; + if let Some(indirect) = self.indirect.as_mut() { + indirect.free(self.count) } - Self { indirect, count } + libc::free(self.indirect.cast()); + self.indirect = ptr::null_mut(); + } + } +} + +/// 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. +#[repr(transparent)] +pub struct MessageIndirector { + base: [*mut RawMessage; 0], + _marker: Immovable, +} + +impl MessageIndirector { + /// Allocates memory for this indirector and all its members. + fn alloc(count: usize) -> *mut Self { + // SAFETY: We're only allocating, and when we're done, + // everything will be in a known-good state. + unsafe { + let me_ptr: *mut MessageIndirector = + libc::calloc(count, size_of::<*mut RawMessage>()).cast(); + 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 = libc::calloc(1, size_of::<RawMessage>()).cast(); + } + me } } - /// Gets a reference to the message at the given index. - pub fn get(&self, index: usize) -> Option<&RawMessage> { - (index < self.count).then(|| unsafe { (*self.indirect).at(index) }) + /// 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 = &RawMessage> { + (0..count).map(|idx| &**self.base.as_ptr().add(idx)) } - /// Gets a mutable reference to the message at the given index. - pub fn get_mut(&mut self, index: usize) -> Option<&mut RawMessage> { - (index < self.count).then(|| unsafe { (*self.indirect).at_mut(index) }) + /// 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 RawMessage> { + (0..count).map(|idx| &mut **self.base.as_mut_ptr().add(idx)) + } + + /// Frees this and everything it points to. + /// + /// # Safety + /// + /// You have to pass the right size. + unsafe fn free(&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() { + msg.clear(); + } + libc::free(msg.cast()); + *msg = ptr::null_mut(); + } } } -#[repr(transparent)] -struct Indirect<T> { - /// The starting address for the T. - base: *mut T, +impl<'a> TryFrom<&'a RawMessage> for Message<'a> { + type Error = ConversionError; + + /// Retrieves the data stored in this message. + fn try_from(input: &RawMessage) -> StdResult<Message, ConversionError> { + let style: Style = input.style.try_into()?; + // SAFETY: We either allocated this message ourselves or were provided it by PAM. + let result = unsafe { + match style { + Style::PromptEchoOff => Message::MaskedPrompt(input.string_data()?), + Style::PromptEchoOn => Message::Prompt(input.string_data()?), + Style::TextInfo => Message::Info(input.string_data()?), + Style::ErrorMsg => Message::Error(input.string_data()?), + Style::RadioType => Message::Error(input.string_data()?), + Style::BinaryPrompt => input.data.cast::<CBinaryData>().as_ref().map_or_else( + || Message::BinaryPrompt { + data_type: 0, + data: &[], + }, + |data| Message::BinaryPrompt { + data_type: data.data_type(), + data: data.contents(), + }, + ), + } + }; + Ok(result) + } } -impl<T> Indirect<T> { - /// Gets a mutable reference to the element at the given index. - /// - /// # Safety - /// - /// We don't check `index`. - unsafe fn at_mut(&mut self, index: usize) -> &mut T { - &mut *self.base.add(index) - } +#[cfg(test)] +mod tests { + use crate::conv::Message; + use crate::pam_ffi::message::OwnedMessages; - unsafe fn at(&self, index: usize) -> &T { - &*self.base.add(index) + #[test] + fn test_owned_messages() { + let mut tons_of_messages = OwnedMessages::alloc(10); + let mut msgs: Vec<_> = tons_of_messages.iter_mut().collect(); + assert!(msgs.get(10).is_none()); + let last_msg = &mut msgs[9]; + last_msg.set(Message::MaskedPrompt("hocus pocus")).unwrap(); + let another_msg = &mut msgs[0]; + another_msg + .set(Message::BinaryPrompt { + data: &[5, 4, 3, 2, 1], + data_type: 99, + }) + .unwrap(); + let overwrite = &mut msgs[3]; + overwrite.set(Message::Prompt("what")).unwrap(); + overwrite.set(Message::Prompt("who")).unwrap(); } }