diff 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 diff
--- a/src/libpam/message.rs	Sat Jun 07 18:55:27 2025 -0400
+++ b/src/libpam/message.rs	Sun Jun 08 01:03:46 2025 -0400
@@ -1,24 +1,17 @@
 //! Data and types dealing with PAM messages.
 
 use crate::constants::InvalidEnum;
-use crate::conv::Message;
+use crate::libpam::conversation::OwnedMessage;
 use crate::libpam::memory;
-use crate::libpam::memory::{CBinaryData, Immovable, NulError, TooBigError};
+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::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),
-}
+use crate::conv::{BorrowedBinaryData, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA, RadioQAndA};
 
 /// The C enum values for messages shown to the user.
 #[derive(Debug, PartialEq, FromPrimitive)]
@@ -53,11 +46,15 @@
     }
 }
 
-/// A message sent by PAM or a module to an application.
-/// This message, and its internal data, is owned by the creator
+/// 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 RawMessage {
+pub struct Question {
     /// The style of message to request.
     style: c_int,
     /// A description of the data requested.
@@ -69,8 +66,8 @@
     _marker: Immovable,
 }
 
-impl RawMessage {
-    pub fn set(&mut self, msg: Message) -> StdResult<(), ConversionError> {
+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.
@@ -86,14 +83,25 @@
     /// # Safety
     ///
     /// It's up to you to pass this only on types with a string value.
-    unsafe fn string_data(&self) -> StdResult<&str, Utf8Error> {
+    unsafe fn string_data(&self) -> Result<&str> {
         if self.data.is_null() {
             Ok("")
         } else {
-            CStr::from_ptr(self.data.cast()).to_str()
+            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.
@@ -119,23 +127,61 @@
     }
 }
 
-/// 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(),
-        )),
+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)
     }
 }
 
-/// Abstraction of a list-of-messages to be sent in a PAM conversation.
+/// 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`.
@@ -170,39 +216,39 @@
 /// ```
 ///
 /// ***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.
+pub struct Questions {
+    /// An indirection to the questions themselves, stored on the C heap.
+    indirect: *mut Indirect,
+    /// The number of questions.
     count: usize,
 }
 
-impl OwnedMessages {
-    /// Allocates data to store messages on the C heap.
+impl Questions {
+    /// Allocates data to store questions on the C heap.
     pub fn alloc(count: usize) -> Self {
         Self {
-            indirect: MessageIndirector::alloc(count),
+            indirect: Indirect::alloc(count),
             count,
         }
     }
 
     /// The pointer to the thing with the actual list.
-    pub fn indirector(&self) -> *const MessageIndirector {
+    pub fn indirect(&self) -> *const Indirect {
         self.indirect
     }
 
-    pub fn iter(&self) -> impl Iterator<Item = &RawMessage> {
+    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 RawMessage> {
+    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 OwnedMessages {
+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.
@@ -221,23 +267,22 @@
 /// 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],
+pub struct Indirect {
+    base: [*mut Question; 0],
     _marker: Immovable,
 }
 
-impl MessageIndirector {
+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 MessageIndirector =
-                libc::calloc(count, size_of::<*mut RawMessage>()).cast();
+            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::<RawMessage>()).cast();
+                *entry = libc::calloc(1, size_of::<Question>()).cast();
             }
             me
         }
@@ -248,7 +293,7 @@
     /// # Safety
     ///
     /// You have to provide the right count.
-    pub unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &RawMessage> {
+    pub unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &Question> {
         (0..count).map(|idx| &**self.base.as_ptr().add(idx))
     }
 
@@ -257,7 +302,7 @@
     /// # Safety
     ///
     /// You have to provide the right count.
-    pub unsafe fn iter_mut(&mut self, count: usize) -> impl Iterator<Item = &mut RawMessage> {
+    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))
     }
 
@@ -278,57 +323,26 @@
     }
 }
 
-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;
+    use super::{MaskedQAndA, Questions};
+    use crate::conv::{BinaryQAndA, QAndA};
 
     #[test]
     fn test_owned_messages() {
-        let mut tons_of_messages = OwnedMessages::alloc(10);
+        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.set(Message::MaskedPrompt("hocus pocus")).unwrap();
+        last_msg
+            .fill(&MaskedQAndA::new("hocus pocus").message())
+            .unwrap();
         let another_msg = &mut msgs[0];
         another_msg
-            .set(Message::BinaryPrompt {
-                data: &[5, 4, 3, 2, 1],
-                data_type: 99,
-            })
+            .fill(&BinaryQAndA::new(&[5, 4, 3, 2, 1], 66).message())
             .unwrap();
         let overwrite = &mut msgs[3];
-        overwrite.set(Message::Prompt("what")).unwrap();
-        overwrite.set(Message::Prompt("who")).unwrap();
+        overwrite.fill(&QAndA::new("what").message()).unwrap();
+        overwrite.fill(&QAndA::new("who").message()).unwrap();
     }
 }