Mercurial > crates > nonstick
view src/libpam/answer.rs @ 79:2128123b9406
Format (oops!) and make some fun and/or stupid conversions available.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sun, 08 Jun 2025 04:21:58 -0400 |
parents | 002adfb98c5c |
children | 5aa1a010f1e8 |
line wrap: on
line source
//! Types used to communicate data from the application to the module. use crate::libpam::conversation::OwnedMessage; use crate::libpam::memory; use crate::libpam::memory::{CBinaryData, Immovable}; use crate::{ErrorCode, Result}; use std::ffi::{c_int, c_void, CStr}; use std::ops::{Deref, DerefMut}; use std::{iter, mem, ptr, slice}; /// The corridor via which the answer to Messages navigate through PAM. #[derive(Debug)] pub struct Answers { base: *mut Answer, count: usize, } impl Answers { /// Builds an Answers out of the given answered Message Q&As. pub fn build(value: Vec<OwnedMessage>) -> Result<Self> { let mut outputs = Self { base: memory::calloc(value.len()), count: value.len(), }; // Even if we fail during this process, we still end up freeing // all allocated answer memory. for (input, output) in iter::zip(value, outputs.iter_mut()) { match input { OwnedMessage::MaskedPrompt(p) => TextAnswer::fill(output, p.answer()?.unsecure())?, OwnedMessage::Prompt(p) => TextAnswer::fill(output, &(p.answer()?))?, OwnedMessage::BinaryPrompt(p) => BinaryAnswer::fill(output, (&p.answer()?).into())?, OwnedMessage::Error(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?, OwnedMessage::Info(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?, OwnedMessage::RadioPrompt(p) => TextAnswer::fill(output, &(p.answer()?))?, } } Ok(outputs) } /// Converts this into a `*Answer` for passing to PAM. /// /// This object is consumed and the `Answer` pointer now owns its data. /// It can be recreated with [`Self::from_c_heap`]. pub fn into_ptr(self) -> *mut Answer { let ret = self.base; mem::forget(self); ret } /// Takes ownership of a list of answers allocated on the C heap. /// /// # Safety /// /// It's up to you to make sure you pass a valid pointer, /// like one that you got from PAM, or maybe [`Self::into_ptr`]. pub unsafe fn from_c_heap(base: *mut Answer, count: usize) -> Self { Answers { base, count } } } impl Deref for Answers { type Target = [Answer]; fn deref(&self) -> &Self::Target { // SAFETY: This is the memory we manage ourselves. unsafe { slice::from_raw_parts(self.base, self.count) } } } impl DerefMut for Answers { fn deref_mut(&mut self) -> &mut Self::Target { // SAFETY: This is the memory we manage ourselves. unsafe { slice::from_raw_parts_mut(self.base, self.count) } } } impl Drop for Answers { fn drop(&mut self) { // SAFETY: We allocated this ourselves, or it was provided to us by PAM. unsafe { for answer in self.iter_mut() { answer.free_contents() } memory::free(self.base) } } } #[repr(transparent)] #[derive(Debug)] pub struct TextAnswer(Answer); impl TextAnswer { /// Interprets the provided `Answer` as a text answer. /// /// # Safety /// /// It's up to you to provide an answer that is a `TextAnswer`. pub unsafe fn upcast(from: &mut Answer) -> &mut Self { // SAFETY: We're provided a valid reference. &mut *(from as *mut Answer).cast::<Self>() } /// Converts the `Answer` to a `TextAnswer` with the given text. fn fill(dest: &mut Answer, text: &str) -> Result<()> { let allocated = memory::malloc_str(text)?; dest.free_contents(); dest.data = allocated.cast(); Ok(()) } /// Gets the string stored in this answer. pub fn contents(&self) -> Result<&str> { if self.0.data.is_null() { Ok("") } else { // SAFETY: This data is either passed from PAM (so we are forced // to trust it) or was created by us in TextAnswerInner::alloc. // In either case, it's going to be a valid null-terminated string. unsafe { CStr::from_ptr(self.0.data.cast()) } .to_str() .map_err(|_| ErrorCode::ConversationError) } } /// Zeroes out the answer data, frees it, and points our data to `null`. /// /// When this `TextAnswer` is part of an [`Answers`], /// this is optional (since that will perform the `free`), /// but it will clear potentially sensitive data. pub fn free_contents(&mut self) { // SAFETY: We own this data and know it's valid. // If it's null, this is a no-op. // After we're done, it will be null. unsafe { memory::zero_c_string(self.0.data.cast()); memory::free(self.0.data); self.0.data = ptr::null_mut() } } } /// A [`Answer`] with [`CBinaryData`] in it. #[repr(transparent)] #[derive(Debug)] pub struct BinaryAnswer(Answer); impl BinaryAnswer { /// Interprets the provided [`Answer`] as a binary answer. /// /// # Safety /// /// It's up to you to provide an answer that is a `BinaryAnswer`. pub unsafe fn upcast(from: &mut Answer) -> &mut Self { // SAFETY: We're provided a valid reference. &mut *(from as *mut Answer).cast::<Self>() } /// Fills in a [`Answer`] with the provided binary data. /// /// The `data_type` is a tag you can use for whatever. /// It is passed through PAM unchanged. /// /// The referenced data is copied to the C heap. /// We do not take ownership of the original data. pub fn fill(dest: &mut Answer, data_and_type: (&[u8], u8)) -> Result<()> { let allocated = CBinaryData::alloc(data_and_type)?; dest.free_contents(); dest.data = allocated.cast(); Ok(()) } /// Gets the binary data in this answer. pub fn data(&self) -> Option<&CBinaryData> { // SAFETY: We either got this data from PAM or allocated it ourselves. // Either way, we trust that it is either valid data or null. unsafe { self.0.data.cast::<CBinaryData>().as_ref() } } /// Zeroes out the answer data, frees it, and points our data to `null`. /// /// When this `TextAnswer` is part of an [`Answers`], /// this is optional (since that will perform the `free`), /// but it will clear potentially sensitive data. pub fn zero_contents(&mut self) { // SAFETY: We know that our data pointer is either valid or null. // Once we're done, it's null and the answer is safe. unsafe { let data_ref = self.0.data.cast::<CBinaryData>().as_mut(); if let Some(d) = data_ref { d.zero_contents() } memory::free(self.0.data); self.0.data = ptr::null_mut() } } } /// Generic version of answer data. /// /// This has the same structure as [`BinaryAnswer`] /// and [`TextAnswer`]. #[repr(C)] #[derive(Debug)] pub struct Answer { /// Pointer to the data returned in an answer. /// For most answers, this will be a [`CStr`], but for answers to /// [`MessageStyle::BinaryPrompt`]s, this will be [`CBinaryData`] /// (a Linux-PAM extension). data: *mut c_void, /// Unused. return_code: c_int, _marker: Immovable, } impl Answer { /// Frees the contents of this answer. /// /// After this is done, this answer's `data` will be `null`, /// which is a valid (empty) state. fn free_contents(&mut self) { // SAFETY: We have either an owned valid pointer, or null. // We can free our owned pointer, and `free(null)` is a no-op. unsafe { memory::free(self.data); self.data = ptr::null_mut(); } } } #[cfg(test)] mod tests { use super::{Answer, Answers, BinaryAnswer, TextAnswer}; use crate::conv::{BinaryQAndA, ErrorMsg, InfoMsg, MaskedQAndA, QAndA, RadioQAndA}; use crate::libpam::conversation::OwnedMessage; use crate::libpam::memory; use crate::BinaryData; #[test] fn test_round_trip() { let binary_msg = { let qa = BinaryQAndA::new((&[][..], 0)); qa.set_answer(Ok(BinaryData::new(vec![1, 2, 3], 99))); OwnedMessage::BinaryPrompt(qa) }; macro_rules! answered { ($typ:ty, $msg:path, $data:expr) => {{ let qa = <$typ>::new(""); qa.set_answer(Ok($data)); $msg(qa) }}; } let answers = vec![ binary_msg, answered!(QAndA, OwnedMessage::Prompt, "whats going on".to_owned()), answered!(MaskedQAndA, OwnedMessage::MaskedPrompt, "well then".into()), answered!(ErrorMsg, OwnedMessage::Error, ()), answered!(InfoMsg, OwnedMessage::Info, ()), answered!( RadioQAndA, OwnedMessage::RadioPrompt, "beep boop".to_owned() ), ]; let n = answers.len(); let sent = Answers::build(answers).unwrap(); let heap_answers = sent.into_ptr(); let mut received = unsafe { Answers::from_c_heap(heap_answers, n) }; let assert_text = |want, raw| { let up = unsafe { TextAnswer::upcast(raw) }; assert_eq!(want, up.contents().unwrap()); up.free_contents(); assert_eq!("", up.contents().unwrap()); }; let assert_bin = |want, raw| { let up = unsafe { BinaryAnswer::upcast(raw) }; assert_eq!(BinaryData::from(want), up.data().into()); up.zero_contents(); assert_eq!(BinaryData::default(), up.data().into()); }; if let [zero, one, two, three, four, five] = &mut received[..] { assert_bin((&[1, 2, 3][..], 99), zero); assert_text("whats going on", one); assert_text("well then", two); assert_text("", three); assert_text("", four); assert_text("beep boop", five); } else { panic!("received wrong size {len}!", len = received.len()) } } #[test] fn test_text_answer() { let answer_ptr: *mut Answer = memory::calloc(1); let answer = unsafe { &mut *answer_ptr }; TextAnswer::fill(answer, "hello").unwrap(); let zeroth_text = unsafe { TextAnswer::upcast(answer) }; let data = zeroth_text.contents().expect("valid"); assert_eq!("hello", data); zeroth_text.free_contents(); zeroth_text.free_contents(); TextAnswer::fill(answer, "hell\0").expect_err("should error; contains nul"); unsafe { memory::free(answer_ptr) } } #[test] fn test_binary_answer() { let answer_ptr: *mut Answer = memory::calloc(1); let answer = unsafe { &mut *answer_ptr }; let real_data = BinaryData::new([1, 2, 3, 4, 5, 6, 7, 8], 9); BinaryAnswer::fill(answer, (&real_data).into()).expect("alloc should succeed"); let bin_answer = unsafe { BinaryAnswer::upcast(answer) }; assert_eq!(real_data, bin_answer.data().into()); answer.free_contents(); answer.free_contents(); unsafe { memory::free(answer_ptr) } } #[test] #[ignore] fn test_binary_answer_too_big() { let big_data: Vec<u8> = vec![0xFFu8; 10_000_000_000]; let answer_ptr: *mut Answer = memory::calloc(1); let answer = unsafe { &mut *answer_ptr }; BinaryAnswer::fill(answer, (&big_data, 100)).expect_err("this is too big!"); answer.free_contents(); unsafe { memory::free(answer) } } }