Mercurial > crates > nonstick
view src/libpam/message.rs @ 75:c30811b4afae
rename pam_ffi submodule to libpam.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Fri, 06 Jun 2025 22:35:08 -0400 |
parents | src/pam_ffi/message.rs@c7c596e6388f |
children | 351bdc13005e |
line wrap: on
line source
//! Data and types dealing with PAM messages. use crate::constants::InvalidEnum; use crate::conv::Message; use crate::libpam::memory; use crate::libpam::memory::{CBinaryData, Immovable, NulError, TooBigError}; use num_derive::FromPrimitive; use num_traits::FromPrimitive; use std::ffi::{c_int, c_void, CStr}; use std::result::Result as StdResult; use std::str::Utf8Error; use std::{ptr, slice}; #[derive(Debug, thiserror::Error)] #[error("error creating PAM message: {0}")] pub enum ConversionError { InvalidEnum(#[from] InvalidEnum<Style>), Utf8Error(#[from] Utf8Error), NulError(#[from] NulError), TooBigError(#[from] TooBigError), } /// 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 From<Style> for c_int { fn from(val: Style) -> Self { val as Self } } /// A message sent by PAM or a module to an application. /// This message, and its internal data, is owned by the creator /// (either the module or PAM itself). #[repr(C)] pub struct RawMessage { /// 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 RawMessage { 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. unsafe { libc::free(self.data) }; self.style = style as c_int; self.data = data; Ok(()) } /// Gets this message's data pointer as a string. /// /// # Safety /// /// It's up to you to pass this only on types with a string value. unsafe fn string_data(&self) -> StdResult<&str, Utf8Error> { if self.data.is_null() { Ok("") } else { CStr::from_ptr(self.data.cast()).to_str() } } /// Zeroes out the data stored here. fn clear(&mut self) { // SAFETY: We either created this data or we got it from PAM. // After this function is done, it will be zeroed out. unsafe { if let Ok(style) = Style::try_from(self.style) { match style { Style::BinaryPrompt => { if let Some(d) = self.data.cast::<CBinaryData>().as_mut() { d.zero_contents() } } Style::TextInfo | Style::RadioType | Style::ErrorMsg | Style::PromptEchoOff | 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::ErrorMsg(text) => alloc(Style::ErrorMsg, text), Message::InfoMsg(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` /// is treated as a pointer-to-pointers, like `int argc, char **argv`. /// (In this situation, the value of `OwnedMessages.indirect` is /// the pointer passed to `pam_conv`.) /// /// ```text /// ╔═ OwnedMsgs ═╗ points to ┌─ Indirect ─┐ ╔═ Message ═╗ /// ║ indirect ┄┄┄╫┄┄┄┄┄┄┄┄┄┄┄> │ base[0] ┄┄┄┼┄┄┄┄┄> ║ style ║ /// ║ count ║ │ base[1] ┄┄┄┼┄┄┄╮ ║ data ║ /// ╚═════════════╝ │ ... │ ┆ ╚═══════════╝ /// ┆ /// ┆ ╔═ Message ═╗ /// ╰┄┄> ║ style ║ /// ║ data ║ /// ╚═══════════╝ /// ``` /// /// On OpenPAM and other compatible implementations (like Solaris), /// `messages` is a pointer-to-pointer-to-array. This appears to be /// the correct implementation as required by the XSSO specification. /// /// ```text /// ╔═ OwnedMsgs ═╗ points to ┌─ Indirect ─┐ ╔═ Message[] ═╗ /// ║ indirect ┄┄┄╫┄┄┄┄┄┄┄┄┄┄┄> │ base ┄┄┄┄┄┄┼┄┄┄┄┄> ║ style ║ /// ║ count ║ └────────────┘ ║ data ║ /// ╚═════════════╝ ╟─────────────╢ /// ║ style ║ /// ║ data ║ /// ╟─────────────╢ /// ║ ... ║ /// ``` /// /// ***THIS LIBRARY CURRENTLY SUPPORTS ONLY LINUX-PAM.*** pub struct OwnedMessages { /// An indirection to the messages themselves, stored on the C heap. indirect: *mut MessageIndirector, /// The number of messages in the list. count: usize, } impl OwnedMessages { /// Allocates data to store messages on the C heap. pub fn alloc(count: usize) -> Self { 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 { if let Some(indirect) = self.indirect.as_mut() { indirect.free(self.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 } } /// 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)) } /// 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(); } } } 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::InfoMsg(input.string_data()?), Style::ErrorMsg => Message::ErrorMsg(input.string_data()?), Style::RadioType => Message::ErrorMsg(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) } } #[cfg(test)] mod tests { use crate::conv::Message; use crate::libpam::message::OwnedMessages; #[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(); } }