# HG changeset patch # User Paul Fisher # Date 1749359026 14400 # Node ID 351bdc13005e8fd9b2988f2cb85f40faead47d6f # Parent e58d24849e829cabd663e1ceb4e6aa3dd4ae1d71 Update the libpam module to work with the new structure. diff -r e58d24849e82 -r 351bdc13005e Cargo.toml --- a/Cargo.toml Sat Jun 07 18:55:27 2025 -0400 +++ b/Cargo.toml Sun Jun 08 01:03:46 2025 -0400 @@ -10,6 +10,7 @@ edition = "2021" [features] +default = ["link"] # Enable this to actually link against your system's PAM library. link = [] diff -r e58d24849e82 -r 351bdc13005e src/constants.rs --- a/src/constants.rs Sat Jun 07 18:55:27 2025 -0400 +++ b/src/constants.rs Sun Jun 08 01:03:46 2025 -0400 @@ -174,9 +174,8 @@ } /// Error returned when attempting to coerce an invalid C integer into an enum. -#[derive(thiserror::Error)] +#[derive(Debug, PartialEq, thiserror::Error)] #[error("{0} is not a valid {type}", type = any::type_name::())] -#[derive(Debug, PartialEq)] pub struct InvalidEnum(c_int, PhantomData); impl From> for c_int { diff -r e58d24849e82 -r 351bdc13005e src/conv.rs --- a/src/conv.rs Sat Jun 07 18:55:27 2025 -0400 +++ b/src/conv.rs Sun Jun 08 01:03:46 2025 -0400 @@ -14,12 +14,12 @@ /// that will be presented to the user. #[non_exhaustive] pub enum Message<'a> { - MaskedPrompt(&'a MaskedPrompt<'a>), - Prompt(&'a Prompt<'a>), - RadioPrompt(&'a RadioPrompt<'a>), - BinaryPrompt(&'a BinaryPrompt<'a>), - InfoMsg(&'a InfoMsg<'a>), - ErrorMsg(&'a ErrorMsg<'a>), + Prompt(&'a QAndA<'a>), + MaskedPrompt(&'a MaskedQAndA<'a>), + RadioPrompt(&'a RadioQAndA<'a>), + BinaryPrompt(&'a BinaryQAndA<'a>), + Error(&'a ErrorMsg<'a>), + Info(&'a InfoMsg<'a>), } impl Message<'_> { @@ -33,11 +33,11 @@ /// /// fn cant_respond(message: Message) { /// match message { - /// Message::InfoMsg(i) => { + /// Message::Info(i) => { /// eprintln!("fyi, {}", i.question()); /// i.set_answer(Ok(())) /// } - /// Message::ErrorMsg(e) => { + /// Message::Error(e) => { /// eprintln!("ERROR: {}", e.question()); /// e.set_answer(Ok(())) /// } @@ -46,55 +46,19 @@ /// } /// } pub fn set_error(&self, err: ErrorCode) { - match self { + match *self { + Message::Prompt(m) => m.set_answer(Err(err)), Message::MaskedPrompt(m) => m.set_answer(Err(err)), - Message::Prompt(m) => m.set_answer(Err(err)), Message::RadioPrompt(m) => m.set_answer(Err(err)), Message::BinaryPrompt(m) => m.set_answer(Err(err)), - Message::InfoMsg(m) => m.set_answer(Err(err)), - Message::ErrorMsg(m) => m.set_answer(Err(err)), + Message::Error(m) => m.set_answer(Err(err)), + Message::Info(m) => m.set_answer(Err(err)), } } } -/// A question-and-answer pair that can be communicated in a [`Conversation`]. -/// -/// The asking side creates a `QAndA`, then converts it to a [`Message`] -/// and sends it via a [`Conversation`]. The Conversation then retrieves -/// the answer to the question (if needed) and sets the response. -/// Once control returns to the asker, the asker gets the answer from this -/// `QAndA` and uses it however it wants. -/// -/// For a more detailed explanation of how this works, -/// see [`Conversation::communicate`]. -pub trait QAndA<'a> { - /// The type of the content of the question. - type Question: Copy; - /// The type of the answer to the question. - type Answer; - - /// Converts this Q-and-A pair into a [`Message`] for the [`Conversation`]. - fn message(&self) -> Message; - - /// The contents of the question being asked. - /// - /// For instance, this might say `"Username:"` to prompt the user - /// for their name. - fn question(&self) -> Self::Question; - - /// Sets the answer to the question. - /// - /// The [`Conversation`] implementation calls this to set the answer. - /// The conversation should *always call this function*, even for messages - /// that don't have "an answer" (like error or info messages). - fn set_answer(&self, answer: Result); - - /// Gets the answer to the question. - fn answer(self) -> Result; -} - macro_rules! q_and_a { - ($name:ident<'a, Q=$qt:ty, A=$at:ty>, $($doc:literal)*) => { + ($name:ident<'a, Q=$qt:ty, A=$at:ty>, $val:path, $($doc:literal)*) => { $( #[doc = $doc] )* @@ -103,25 +67,34 @@ a: Cell>, } - impl<'a> QAndA<'a> for $name<'a> { - type Question = $qt; - type Answer = $at; + impl<'a> $name<'a> { + /// Converts this Q&A into a [`Message`] for the [`Conversation`]. + pub fn message(&self) -> Message { + $val(self) + } - fn question(&self) -> Self::Question { + /// The contents of the question being asked. + /// + /// For instance, this might say `"Username:"` to prompt the user + /// for their name, or the text of an error message. + pub fn question(&self) -> $qt { self.q } - fn set_answer(&self, answer: Result) { + /// Sets the answer to the question. + /// + /// The [`Conversation`] implementation calls this to set the answer. + /// The conversation should *always call this function*, + /// even for Q&A messages that don't have "an answer" + /// (like error or info messages). + pub fn set_answer(&self, answer: Result<$at>) { self.a.set(answer) } - fn answer(self) -> Result { + /// Gets the answer to the question. + pub fn answer(self) -> Result<$at> { self.a.into_inner() } - - fn message(&self) -> Message { - Message::$name(self) - } } }; } @@ -130,7 +103,7 @@ ($t:ident) => { impl<'a> $t<'a> { #[doc = concat!("Creates a `", stringify!($t), "` to be sent to the user.")] - fn ask(question: &'a str) -> Self { + pub fn new(question: &'a str) -> Self { Self { q: question, a: Cell::new(Err(ErrorCode::ConversationError)), @@ -141,35 +114,39 @@ } q_and_a!( - MaskedPrompt<'a, Q=&'a str, A=SecureString>, - "Asks the user for data and does not echo it back while being entered." + MaskedQAndA<'a, Q=&'a str, A=SecureString>, + Message::MaskedPrompt, + "A Q&A that asks the user for text and does not show it while typing." "" "In other words, a password entry prompt." ); -ask!(MaskedPrompt); +ask!(MaskedQAndA); q_and_a!( - Prompt<'a, Q=&'a str, A=String>, - "Asks the user for data." + QAndA<'a, Q=&'a str, A=String>, + Message::Prompt, + "A standard Q&A prompt that asks the user for text." "" "This is the normal \"ask a person a question\" prompt." "When the user types, their input will be shown to them." "It can be used for things like usernames." ); -ask!(Prompt); +ask!(QAndA); q_and_a!( - RadioPrompt<'a, Q=&'a str, A=String>, - "Asks the user for \"radio button\"–style data. (Linux-PAM extension)" + RadioQAndA<'a, Q=&'a str, A=String>, + Message::RadioPrompt, + "A Q&A for \"radio button\"–style data. (Linux-PAM extension)" "" "This message type is theoretically useful for \"yes/no/maybe\"" "questions, but nowhere in the documentation is it specified" "what the format of the answer will be, or how this should be shown." ); -ask!(RadioPrompt); +ask!(RadioQAndA); q_and_a!( - BinaryPrompt<'a, Q=BinaryQuestion<'a>, A=BinaryData>, + BinaryQAndA<'a, Q=BorrowedBinaryData<'a>, A=BinaryData>, + Message::BinaryPrompt, "Asks for binary data. (Linux-PAM extension)" "" "This sends a binary message to the client application." @@ -179,29 +156,44 @@ "The `data_type` tag is a value that is simply passed through" "to the application. PAM does not define any meaning for it." ); -impl<'a> BinaryPrompt<'a> { +impl<'a> BinaryQAndA<'a> { /// Creates a prompt for the given binary data. /// /// The `data_type` is a tag you can use for communication between /// the module and the application. Its meaning is undefined by PAM. - fn ask(data: &'a [u8], data_type: u8) -> Self { + pub fn new(data: &'a [u8], data_type: u8) -> Self { + BorrowedBinaryData { data, data_type }.into() + } +} + +impl<'a> From> for BinaryQAndA<'a> { + fn from(q: BorrowedBinaryData<'a>) -> Self { Self { - q: BinaryQuestion { data, data_type }, + q, a: Cell::new(Err(ErrorCode::ConversationError)), } } } -/// The contents of a question requesting binary data. -/// -/// A borrowed version of [`BinaryData`]. -#[derive(Copy, Clone, Debug)] -pub struct BinaryQuestion<'a> { +impl<'a> From<&'a BinaryData> for BinaryQAndA<'a> { + fn from(src: &'a BinaryData) -> Self { + BorrowedBinaryData::from(src).into() + } +} + +/// A version of [`BinaryData`] where the `data` is borrowed. +#[derive(Copy, Clone, Debug, Default)] +pub struct BorrowedBinaryData<'a> { data: &'a [u8], data_type: u8, } -impl BinaryQuestion<'_> { +impl<'a> BorrowedBinaryData<'a> { + /// Creates a new BinaryQuestion as a view over the given data. + pub fn new(data: &'a [u8], data_type: u8) -> Self { + Self { data, data_type } + } + /// Gets the data of this question. pub fn data(&self) -> &[u8] { self.data @@ -213,11 +205,17 @@ } } +impl<'a> From<&'a BinaryData> for BorrowedBinaryData<'a> { + fn from(value: &'a BinaryData) -> Self { + Self::new(&value.data, value.data_type) + } +} + /// Owned binary data. /// -/// For borrowed data, see [`BinaryQuestion`]. +/// For borrowed data, see [`BorrowedBinaryData`]. /// You can take ownership of the stored data with `.into::>()`. -#[derive(Debug, PartialEq)] +#[derive(Debug, Default, PartialEq)] pub struct BinaryData { data: Vec, data_type: u8, @@ -238,6 +236,15 @@ } } +impl<'a> From> for BinaryData { + fn from(value: BorrowedBinaryData) -> Self { + Self { + data: value.data.to_vec(), + data_type: value.data_type, + } + } +} + impl From for Vec { /// Takes ownership of the data stored herein. fn from(value: BinaryData) -> Self { @@ -247,6 +254,7 @@ q_and_a!( InfoMsg<'a, Q = &'a str, A = ()>, + Message::Info, "A message containing information to be passed to the user." "" "While this does not have an answer, [`Conversation`] implementations" @@ -255,7 +263,7 @@ ); impl<'a> InfoMsg<'a> { /// Creates an informational message to send to the user. - fn new(message: &'a str) -> Self { + pub fn new(message: &'a str) -> Self { Self { q: message, a: Cell::new(Err(ErrorCode::ConversationError)), @@ -265,6 +273,7 @@ q_and_a!( ErrorMsg<'a, Q = &'a str, A = ()>, + Message::Error, "An error message to be passed to the user." "" "While this does not have an answer, [`Conversation`] implementations" @@ -274,7 +283,7 @@ ); impl<'a> ErrorMsg<'a> { /// Creates an error message to send to the user. - fn new(message: &'a str) -> Self { + pub fn new(message: &'a str) -> Self { Self { q: message, a: Cell::new(Err(ErrorCode::ConversationError)), @@ -427,27 +436,27 @@ } macro_rules! conv_fn { - ($fn_name:ident($($param:ident: $pt:ty),+) -> $resp_type:ty { $ask:path }) => { + ($fn_name:ident($($param:ident: $pt:ty),+) -> $resp_type:ty { $msg:ty }) => { fn $fn_name(&mut self, $($param: $pt),*) -> Result<$resp_type> { - let prompt = $ask($($param),*); + let prompt = <$msg>::new($($param),*); self.communicate(&[prompt.message()]); prompt.answer() } }; - ($fn_name:ident($($param:ident: $pt:ty),+) { $ask:path }) => { + ($fn_name:ident($($param:ident: $pt:ty),+) { $msg:ty }) => { fn $fn_name(&mut self, $($param: $pt),*) { - self.communicate(&[$ask($($param),*).message()]); + self.communicate(&[<$msg>::new($($param),*).message()]); } }; } impl SimpleConversation for C { - conv_fn!(prompt(message: &str) -> String { Prompt::ask }); - conv_fn!(masked_prompt(message: &str) -> SecureString { MaskedPrompt::ask }); - conv_fn!(radio_prompt(message: &str) -> String { RadioPrompt::ask }); - conv_fn!(error_msg(message: &str) { ErrorMsg::new }); - conv_fn!(info_msg(message: &str) { InfoMsg::new }); - conv_fn!(binary_prompt(data: &[u8], data_type: u8) -> BinaryData { BinaryPrompt::ask }); + conv_fn!(prompt(message: &str) -> String { QAndA }); + conv_fn!(masked_prompt(message: &str) -> SecureString { MaskedQAndA } ); + conv_fn!(radio_prompt(message: &str) -> String { RadioQAndA }); + conv_fn!(error_msg(message: &str) { ErrorMsg }); + conv_fn!(info_msg(message: &str) { InfoMsg }); + conv_fn!(binary_prompt(data: &[u8], data_type: u8) -> BinaryData { BinaryQAndA }); } /// A [`Conversation`] which asks the questions one at a time. @@ -466,11 +475,11 @@ Message::RadioPrompt(prompt) => { prompt.set_answer(self.0.radio_prompt(prompt.question())) } - Message::InfoMsg(prompt) => { + Message::Info(prompt) => { self.0.info_msg(prompt.question()); prompt.set_answer(Ok(())) } - Message::ErrorMsg(prompt) => { + Message::Error(prompt) => { self.0.error_msg(prompt.question()); prompt.set_answer(Ok(())) } @@ -486,8 +495,8 @@ #[cfg(test)] mod tests { use super::{ - BinaryPrompt, Conversation, ErrorMsg, InfoMsg, MaskedPrompt, Message, Prompt, QAndA, - RadioPrompt, Result, SecureString, SimpleConversation, + BinaryQAndA, Conversation, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA, + RadioQAndA, Result, SecureString, SimpleConversation, }; use crate::constants::ErrorCode; use crate::BinaryData; @@ -533,11 +542,11 @@ let mut tester = DemuxTester::default(); - let what = Prompt::ask("what"); - let pass = MaskedPrompt::ask("reveal"); + let what = QAndA::new("what"); + let pass = MaskedQAndA::new("reveal"); let err = ErrorMsg::new("whoopsie"); let info = InfoMsg::new("did you know"); - let has_err = Prompt::ask("give_err"); + let has_err = QAndA::new("give_err"); let mut conv = tester.as_conversation(); @@ -563,8 +572,8 @@ let mut conv = tester.as_conversation(); - let radio = RadioPrompt::ask("channel?"); - let bin = BinaryPrompt::ask(&[10, 9, 8], 66); + let radio = RadioQAndA::new("channel?"); + let bin = BinaryQAndA::new(&[10, 9, 8], 66); conv.communicate(&[radio.message(), bin.message()]); assert_eq!("zero", radio.answer().unwrap()); @@ -578,11 +587,11 @@ fn communicate(&mut self, messages: &[Message]) { if let [msg] = messages { match *msg { - Message::InfoMsg(info) => { + Message::Info(info) => { assert_eq!("let me tell you", info.question()); info.set_answer(Ok(())) } - Message::ErrorMsg(error) => { + Message::Error(error) => { assert_eq!("oh no", error.question()); error.set_answer(Ok(())) } diff -r e58d24849e82 -r 351bdc13005e src/libpam/conversation.rs --- a/src/libpam/conversation.rs Sat Jun 07 18:55:27 2025 -0400 +++ b/src/libpam/conversation.rs Sun Jun 08 01:03:46 2025 -0400 @@ -1,14 +1,15 @@ -use crate::constants::Result; -use crate::conv::{Conversation, Message, Response}; +use crate::conv::{ + BinaryQAndA, Conversation, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA, + RadioQAndA, +}; use crate::libpam::memory::Immovable; -use crate::libpam::message::{MessageIndirector, OwnedMessages}; -use crate::libpam::response::{OwnedResponses, RawBinaryResponse, RawResponse, RawTextResponse}; +use crate::libpam::message::{Indirect, Questions}; +use crate::libpam::response::{Answer, Answers, BinaryAnswer, TextAnswer}; use crate::ErrorCode; -use crate::ErrorCode::ConversationError; +use crate::Result; use std::ffi::c_int; use std::iter; use std::marker::PhantomData; -use std::result::Result as StdResult; /// An opaque structure that is passed through PAM in a conversation. #[repr(C)] @@ -23,15 +24,15 @@ /// - `messages` is a pointer to the messages being sent to the user. /// For details about its structure, see the documentation of /// [`OwnedMessages`](super::OwnedMessages). -/// - `responses` is a pointer to an array of [`RawResponse`]s, +/// - `responses` is a pointer to an array of [`Answer`]s, /// which PAM sets in response to a module's request. /// This is an array of structs, not an array of pointers to a struct. /// There should always be exactly as many `responses` as `num_msg`. /// - `appdata` is the `appdata` field of the [`LibPamConversation`] we were passed. pub type ConversationCallback = unsafe extern "C" fn( num_msg: c_int, - messages: *const MessageIndirector, - responses: *mut *mut RawResponse, + messages: *const Indirect, + responses: *mut *mut Answer, appdata: *mut AppData, ) -> c_int; @@ -59,104 +60,116 @@ unsafe extern "C" fn wrapper_callback( count: c_int, - messages: *const MessageIndirector, - responses: *mut *mut RawResponse, + questions: *const Indirect, + answers: *mut *mut Answer, me: *mut AppData, ) -> c_int { - let call = || { + let internal = || { + // Collect all our pointers let conv = me .cast::() .as_mut() .ok_or(ErrorCode::ConversationError)?; - let indir = messages.as_ref().ok_or(ErrorCode::ConversationError)?; - let response_ptr = responses.as_mut().ok_or(ErrorCode::ConversationError)?; - let messages: Vec = indir + let indirect = questions.as_ref().ok_or(ErrorCode::ConversationError)?; + let answers_ptr = answers.as_mut().ok_or(ErrorCode::ConversationError)?; + + // Build our owned list of Q&As from the questions we've been asked + let messages: Vec = indirect .iter(count as usize) - .map(Message::try_from) - .collect::>() + .map(OwnedMessage::try_from) + .collect::>() .map_err(|_| ErrorCode::ConversationError)?; - let responses = conv.communicate(&messages)?; - let owned = - OwnedResponses::build(&responses).map_err(|_| ErrorCode::ConversationError)?; - *response_ptr = owned.into_ptr(); + // Borrow all those Q&As and ask them + let borrowed: Vec = messages.iter().map(Into::into).collect(); + conv.communicate(&borrowed); + + // Send our answers back + let owned = Answers::build(messages).map_err(|_| ErrorCode::ConversationError)?; + *answers_ptr = owned.into_ptr(); Ok(()) }; - ErrorCode::result_to_c(call()) + ErrorCode::result_to_c(internal()) } } impl Conversation for LibPamConversation<'_> { - fn communicate(&mut self, messages: &[Message]) -> Result> { - let mut msgs_to_send = OwnedMessages::alloc(messages.len()); - for (dst, src) in iter::zip(msgs_to_send.iter_mut(), messages.iter()) { - dst.set(*src).map_err(|_| ErrorCode::ConversationError)? + fn communicate(&mut self, messages: &[Message]) { + let internal = || { + let mut msgs_to_send = Questions::alloc(messages.len()); + for (dst, src) in iter::zip(msgs_to_send.iter_mut(), messages.iter()) { + dst.fill(src).map_err(|_| ErrorCode::ConversationError)? + } + let mut response_pointer = std::ptr::null_mut(); + // SAFETY: We're calling into PAM with valid everything. + let result = unsafe { + (self.callback)( + messages.len() as c_int, + msgs_to_send.indirect(), + &mut response_pointer, + self.appdata, + ) + }; + ErrorCode::result_from(result)?; + // SAFETY: This is a pointer we just got back from PAM. + // We have to trust that the responses from PAM match up + // with the questions we sent. + unsafe { + let mut owned_responses = Answers::from_c_heap(response_pointer, messages.len()); + for (msg, response) in iter::zip(messages, owned_responses.iter_mut()) { + convert(msg, response); + } + }; + Ok(()) + }; + if let Err(e) = internal() { + messages.iter().for_each(|m| m.set_error(e)) } - let mut response_pointer = std::ptr::null_mut(); - // SAFETY: We're calling into PAM with valid everything. - let result = unsafe { - (self.callback)( - messages.len() as c_int, - msgs_to_send.indirector(), - &mut response_pointer, - self.appdata, - ) - }; - ErrorCode::result_from(result)?; - // SAFETY: This is a pointer we just got back from PAM. - let owned_responses = - unsafe { OwnedResponses::from_c_heap(response_pointer, messages.len()) }; - convert_responses(messages, owned_responses) } } -fn convert_responses( - messages: &[Message], - mut raw_responses: OwnedResponses, -) -> Result> { - let pairs = iter::zip(messages.iter(), raw_responses.iter_mut()); - // We first collect into a Vec of Results so that we always process - // every single entry, which may involve freeing it. - let responses: Vec<_> = pairs.map(convert).collect(); - // Only then do we return the first error, if present. - responses.into_iter().collect() +/// Like [`Message`], but this time we own the contents. +pub enum OwnedMessage<'a> { + MaskedPrompt(MaskedQAndA<'a>), + Prompt(QAndA<'a>), + RadioPrompt(RadioQAndA<'a>), + BinaryPrompt(BinaryQAndA<'a>), + Info(InfoMsg<'a>), + Error(ErrorMsg<'a>), +} + +impl<'a> From<&'a OwnedMessage<'a>> for Message<'a> { + fn from(src: &'a OwnedMessage) -> Self { + match src { + OwnedMessage::MaskedPrompt(m) => Message::MaskedPrompt(m), + OwnedMessage::Prompt(m) => Message::Prompt(m), + OwnedMessage::RadioPrompt(m) => Message::RadioPrompt(m), + OwnedMessage::BinaryPrompt(m) => Message::BinaryPrompt(m), + OwnedMessage::Info(m) => Message::Info(m), + OwnedMessage::Error(m) => Message::Error(m), + } + } } -/// Converts one message-to-raw pair to a Response. -fn convert((sent, received): (&Message, &mut RawResponse)) -> Result { - Ok(match sent { - Message::MaskedPrompt(_) => { - // SAFETY: Since this is a response to a text message, - // we know it is text. - let text_resp = unsafe { RawTextResponse::upcast(received) }; - let ret = Response::MaskedText( - text_resp - .contents() - .map_err(|_| ErrorCode::ConversationError)? - .into(), - ); - // SAFETY: We're the only ones using this, - // and we haven't freed it. - text_resp.free_contents(); - ret +/// Fills in the answer of the Message with the given response. +/// +/// # Safety +/// +/// You are responsible for ensuring that the src-dst pair matches. +unsafe fn convert(msg: &Message, resp: &mut Answer) { + macro_rules! fill_text { + ($dst:ident, $src:ident) => {{let text_resp = unsafe {TextAnswer::upcast($src)}; + $dst.set_answer(text_resp.contents().map(Into::into));}} +} + match *msg { + Message::MaskedPrompt(qa) => fill_text!(qa, resp), + Message::Prompt(qa) => fill_text!(qa, resp), + Message::RadioPrompt(qa) => fill_text!(qa, resp), + Message::Error(m) => m.set_answer(Ok(())), + Message::Info(m) => m.set_answer(Ok(())), + Message::BinaryPrompt(qa) => { + let bin_resp = unsafe { BinaryAnswer::upcast(resp) }; + qa.set_answer(Ok(bin_resp.data().into())); + bin_resp.zero_contents() } - Message::Prompt(_) | Message::RadioPrompt(_) => { - // SAFETY: Since this is a response to a text message, - // we know it is text. - let text_resp = unsafe { RawTextResponse::upcast(received) }; - let ret = Response::Text(text_resp.contents().map_err(|_| ConversationError)?.into()); - // SAFETY: We're the only ones using this, - // and we haven't freed it. - text_resp.free_contents(); - ret - } - Message::ErrorMsg(_) | Message::InfoMsg(_) => Response::NoResponse, - Message::BinaryPrompt { .. } => { - let bin_resp = unsafe { RawBinaryResponse::upcast(received) }; - let ret = Response::Binary(bin_resp.to_owned()); - // SAFETY: We're the only ones using this, - // and we haven't freed it. - bin_resp.free_contents(); - ret - } - }) + } } diff -r e58d24849e82 -r 351bdc13005e src/libpam/handle.rs --- a/src/libpam/handle.rs Sat Jun 07 18:55:27 2025 -0400 +++ b/src/libpam/handle.rs Sun Jun 08 01:03:46 2025 -0400 @@ -4,7 +4,7 @@ use crate::handle::{PamApplicationOnly, PamModuleOnly, PamShared}; use crate::libpam::memory; use crate::libpam::memory::Immovable; -use crate::{Conversation, Response}; +use crate::Conversation; use num_derive::FromPrimitive; use num_traits::FromPrimitive; use std::ffi::{c_char, c_int}; @@ -144,8 +144,15 @@ } impl Conversation for LibPamHandle { - fn communicate(&mut self, messages: &[Message]) -> Result> { - self.conversation_item()?.communicate(messages) + fn communicate(&mut self, messages: &[Message]) { + match self.conversation_item() { + Ok(conv) => conv.communicate(messages), + Err(e) => { + for msg in messages { + msg.set_error(e) + } + } + } } } diff -r e58d24849e82 -r 351bdc13005e src/libpam/memory.rs --- a/src/libpam/memory.rs Sat Jun 07 18:55:27 2025 -0400 +++ b/src/libpam/memory.rs Sun Jun 08 01:03:46 2025 -0400 @@ -1,10 +1,10 @@ //! Things for dealing with memory. -use crate::ErrorCode; +use crate::conv::BorrowedBinaryData; use crate::Result; +use crate::{BinaryData, ErrorCode}; use std::ffi::{c_char, c_void, CStr, CString}; use std::marker::{PhantomData, PhantomPinned}; -use std::result::Result as StdResult; use std::{ptr, slice}; /// Makes whatever it's in not [`Send`], [`Sync`], or [`Unpin`]. @@ -66,10 +66,10 @@ /// /// - it allocates data on the C heap with [`libc::malloc`]. /// - it doesn't take ownership of the data passed in. -pub fn malloc_str(text: impl AsRef) -> StdResult<*mut c_char, NulError> { - let data = text.as_ref().as_bytes(); - if let Some(nul) = data.iter().position(|x| *x == 0) { - return Err(NulError(nul)); +pub fn malloc_str(text: &str) -> Result<*mut c_char> { + let data = text.as_bytes(); + if data.contains(&0) { + return Err(ErrorCode::ConversationError); } unsafe { let data_alloc = libc::calloc(data.len() + 1, 1); @@ -110,11 +110,9 @@ impl CBinaryData { /// Copies the given data to a new BinaryData on the heap. - pub fn alloc(source: &[u8], data_type: u8) -> StdResult<*mut CBinaryData, TooBigError> { - let buffer_size = u32::try_from(source.len() + 5).map_err(|_| TooBigError { - max: (u32::MAX - 5) as usize, - actual: source.len(), - })?; + pub fn alloc(source: &[u8], data_type: u8) -> Result<*mut CBinaryData> { + let buffer_size = + u32::try_from(source.len() + 5).map_err(|_| ErrorCode::ConversationError)?; // SAFETY: We're only allocating here. let data = unsafe { let dest_buffer: *mut CBinaryData = libc::malloc(buffer_size as usize).cast(); @@ -132,13 +130,6 @@ u32::from_be_bytes(self.total_length).saturating_sub(5) as usize } - pub fn contents(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.data.as_ptr(), self.length()) } - } - pub fn data_type(&self) -> u8 { - self.data_type - } - /// Clears this data and frees it. pub unsafe fn zero_contents(&mut self) { let contents = slice::from_raw_parts_mut(self.data.as_mut_ptr(), self.length()); @@ -150,16 +141,19 @@ } } -#[derive(Debug, thiserror::Error)] -#[error("null byte within input at byte {0}")] -pub struct NulError(pub usize); +impl<'a> From<&'a CBinaryData> for BorrowedBinaryData<'a> { + fn from(value: &'a CBinaryData) -> Self { + BorrowedBinaryData::new( + unsafe { slice::from_raw_parts(value.data.as_ptr(), value.length()) }, + value.data_type, + ) + } +} -/// Returned when trying to fit too much data into a binary message. -#[derive(Debug, thiserror::Error)] -#[error("cannot create a message of {actual} bytes; maximum is {max}")] -pub struct TooBigError { - pub actual: usize, - pub max: usize, +impl From> for BinaryData { + fn from(value: Option<&CBinaryData>) -> Self { + value.map(BorrowedBinaryData::from).map(Into::into).unwrap_or_default() + } } #[cfg(test)] diff -r e58d24849e82 -r 351bdc13005e src/libpam/message.rs --- 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