Mercurial > crates > nonstick
view src/libpam/message.rs @ 77:351bdc13005e
Update the libpam module to work with the new structure.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sun, 08 Jun 2025 01:03:46 -0400 |
parents | c30811b4afae |
children |
line wrap: on
line source
//! Data and types dealing with PAM messages. use crate::constants::InvalidEnum; use crate::libpam::conversation::OwnedMessage; use crate::libpam::memory; use crate::libpam::memory::{CBinaryData, Immovable}; 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::{ptr, slice}; use crate::conv::{BorrowedBinaryData, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA, RadioQAndA}; /// 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 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 { pub fn fill(&mut self, msg: &Message) -> Result<()> { 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) -> Result<&str> { if self.data.is_null() { Ok("") } else { CStr::from_ptr(self.data.cast()) .to_str() .map_err(|_| ErrorCode::ConversationError) } } /// Gets this message's data pointer as borrowed binary data. unsafe fn binary_data(&self) -> BorrowedBinaryData { self.data .cast::<CBinaryData>() .as_ref() .map(Into::into) .unwrap_or_default() } /// 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(); } } } impl<'a> TryFrom<&'a Question> for OwnedMessage<'a> { type Error = ErrorCode; fn try_from(question: &'a Question) -> Result<Self> { let style: Style = question .style .try_into() .map_err(|_| ErrorCode::ConversationError)?; // SAFETY: In all cases below, we're matching the let prompt = unsafe { match style { Style::PromptEchoOff => { Self::MaskedPrompt(MaskedQAndA::new(question.string_data()?)) } Style::PromptEchoOn => Self::Prompt(QAndA::new(question.string_data()?)), Style::ErrorMsg => Self::Error(ErrorMsg::new(question.string_data()?)), Style::TextInfo => Self::Info(InfoMsg::new(question.string_data()?)), Style::RadioType => Self::RadioPrompt(RadioQAndA::new(question.string_data()?)), Style::BinaryPrompt => Self::BinaryPrompt(question.binary_data().into()), } }; Ok(prompt) } } /// Copies the contents of this message to the C heap. fn copy_to_heap(msg: &Message) -> Result<(Style, *mut c_void)> { let alloc = |style, text| Ok((style, memory::malloc_str(text)?.cast())); match *msg { Message::MaskedPrompt(p) => alloc(Style::PromptEchoOff, p.question()), Message::Prompt(p) => alloc(Style::PromptEchoOn, p.question()), Message::RadioPrompt(p) => alloc(Style::RadioType, p.question()), Message::Error(p) => alloc(Style::ErrorMsg, p.question()), Message::Info(p) => alloc(Style::TextInfo, p.question()), Message::BinaryPrompt(p) => { let q = p.question(); Ok(( Style::BinaryPrompt, CBinaryData::alloc(q.data(), q.data_type())?.cast(), )) } } } /// Abstraction of a collection of questions to be sent in a PAM conversation. /// /// The PAM C API conversation function looks like this: /// /// ```c /// int pam_conv( /// int count, /// const struct pam_message **questions, /// struct pam_response **answers, /// void *appdata_ptr, /// ) /// ``` /// /// 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 Questions { /// An indirection to the questions themselves, stored on the C heap. indirect: *mut Indirect, /// The number of questions. count: usize, } impl Questions { /// Allocates data to store questions on the C heap. pub fn alloc(count: usize) -> Self { Self { indirect: Indirect::alloc(count), count, } } /// The pointer to the thing with the actual list. pub fn indirect(&self) -> *const Indirect { self.indirect } pub fn iter(&self) -> impl Iterator<Item = &Question> { // 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 Question> { // SAFETY: we're iterating over an amount we know. unsafe { (*self.indirect).iter_mut(self.count) } } } impl Drop for Questions { 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 Indirect { base: [*mut Question; 0], _marker: Immovable, } impl Indirect { /// 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 Indirect = libc::calloc(count, size_of::<*mut Question>()).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::<Question>()).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 = &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> { (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(); } } } #[cfg(test)] mod tests { use super::{MaskedQAndA, Questions}; use crate::conv::{BinaryQAndA, QAndA}; #[test] fn test_owned_messages() { let mut tons_of_messages = Questions::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 .fill(&MaskedQAndA::new("hocus pocus").message()) .unwrap(); let another_msg = &mut msgs[0]; another_msg .fill(&BinaryQAndA::new(&[5, 4, 3, 2, 1], 66).message()) .unwrap(); let overwrite = &mut msgs[3]; overwrite.fill(&QAndA::new("what").message()).unwrap(); overwrite.fill(&QAndA::new("who").message()).unwrap(); } }