Mercurial > crates > nonstick
diff src/libpam/response.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/response.rs Sat Jun 07 18:55:27 2025 -0400 +++ b/src/libpam/response.rs Sun Jun 08 01:03:46 2025 -0400 @@ -1,55 +1,59 @@ -//! Types used when dealing with PAM conversations. +//! Types used to communicate data from the application to the module. -use crate::conv::BinaryData; +use crate::conv::BorrowedBinaryData; +use crate::libpam::conversation::OwnedMessage; use crate::libpam::memory; -use crate::libpam::memory::{CBinaryData, Immovable, NulError, TooBigError}; -use crate::Response; +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::result::Result as StdResult; -use std::str::Utf8Error; use std::{iter, mem, ptr, slice}; #[repr(transparent)] #[derive(Debug)] -pub struct RawTextResponse(RawResponse); +pub struct TextAnswer(Answer); -impl RawTextResponse { - /// Interprets the provided `RawResponse` as a text response. +impl TextAnswer { + /// Interprets the provided `Answer` as a text answer. /// /// # Safety /// - /// It's up to you to provide a response that is a `RawTextResponse`. - pub unsafe fn upcast(from: &mut RawResponse) -> &mut Self { + /// 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 RawResponse).cast::<Self>() + &mut *(from as *mut Answer).cast::<Self>() } - /// Fills in the provided `RawResponse` with the given text. - /// - /// You are responsible for calling [`free`](Self::free_contents) - /// on the pointer you get back when you're done with it. - pub fn fill(dest: &mut RawResponse, text: impl AsRef<str>) -> StdResult<&mut Self, NulError> { - dest.data = memory::malloc_str(text)?.cast(); - // SAFETY: We just filled this in so we know it's a text response. - Ok(unsafe { Self::upcast(dest) }) + /// 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 response. - pub fn contents(&self) -> StdResult<&str, Utf8Error> { + /// 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 TextResponseInner::alloc. + // 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() + unsafe { CStr::from_ptr(self.0.data.cast()) } + .to_str() + .map_err(|_| ErrorCode::ConversationError) } } - /// Releases memory owned by this response. + /// 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 know we own this data. + // 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); @@ -59,65 +63,51 @@ } } -/// A [`RawResponse`] with [`CBinaryData`] in it. +/// A [`Answer`] with [`CBinaryData`] in it. #[repr(transparent)] #[derive(Debug)] -pub struct RawBinaryResponse(RawResponse); +pub struct BinaryAnswer(Answer); -impl RawBinaryResponse { - /// Interprets the provided `RawResponse` as a binary response. +impl BinaryAnswer { + /// Interprets the provided [`Answer`] as a binary answer. /// /// # Safety /// - /// It's up to you to provide a response that is a `RawBinaryResponse`. - pub unsafe fn upcast(from: &mut RawResponse) -> &mut Self { + /// 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 RawResponse).cast::<Self>() + &mut *(from as *mut Answer).cast::<Self>() } - /// Fills in a `RawResponse` with the provided binary data. + /// 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. - /// You are responsible for calling [`free`](Self::free_contents) - /// on the pointer you get back when you're done with it. - pub fn fill<'a>( - dest: &'a mut RawResponse, - data: &[u8], - data_type: u8, - ) -> StdResult<&'a mut Self, TooBigError> { - dest.data = CBinaryData::alloc(data, data_type)?.cast(); - // SAFETY: We just filled this in, so we know it's binary. - Ok(unsafe { Self::upcast(dest) }) + /// 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: BorrowedBinaryData) -> Result<()> { + let allocated = CBinaryData::alloc(data.data(), data.data_type())?; + dest.free_contents(); + dest.data = allocated.cast(); + Ok(()) } - /// Gets the binary data in this response. - pub fn data(&self) -> &[u8] { - self.contents().map(CBinaryData::contents).unwrap_or(&[]) - } - - /// Gets the `data_type` tag that was embedded with the message. - pub fn data_type(&self) -> u8 { - self.contents().map(CBinaryData::data_type).unwrap_or(0) - } - - fn contents(&self) -> Option<&CBinaryData> { - // SAFETY: This was either something we got from PAM (in which case - // we trust it), or something that was created with - // BinaryResponseInner::alloc. In both cases, it points to valid data. + /// 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() } } - pub fn to_owned(&self) -> BinaryData { - BinaryData::new(self.data().into(), self.data_type()) - } - - /// Releases memory owned by this response. - pub fn free_contents(&mut self) { + /// 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 response is safe. + // 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 { @@ -129,15 +119,15 @@ } } -/// Generic version of response data. +/// Generic version of answer data. /// -/// This has the same structure as [`RawBinaryResponse`] -/// and [`RawTextResponse`]. +/// This has the same structure as [`BinaryAnswer`] +/// and [`TextAnswer`]. #[repr(C)] #[derive(Debug)] -pub struct RawResponse { - /// Pointer to the data returned in a response. - /// For most responses, this will be a [`CStr`], but for responses to +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, @@ -146,94 +136,95 @@ _marker: Immovable, } -/// A contiguous block of responses. +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 { + libc::free(self.data); + self.data = ptr::null_mut(); + } + } +} + +/// An owned, contiguous block of [`Answer`]s. #[derive(Debug)] -pub struct OwnedResponses { - base: *mut RawResponse, +pub struct Answers { + base: *mut Answer, count: usize, } -impl OwnedResponses { - /// Allocates an owned list of responses on the C heap. +impl Answers { + /// Allocates an owned list of answers on the C heap. fn alloc(count: usize) -> Self { - OwnedResponses { + Answers { // SAFETY: We are doing allocation here. - base: unsafe { libc::calloc(count, size_of::<RawResponse>()) }.cast(), + base: unsafe { libc::calloc(count, size_of::<Answer>()) }.cast(), count, } } - pub fn build(value: &[Response]) -> StdResult<Self, FillError> { - let mut outputs = OwnedResponses::alloc(value.len()); - // If we fail in here after allocating OwnedResponses, - // we still free all memory, even though we don't zero it first. - // This is an acceptable level of risk. - for (input, output) in iter::zip(value.iter(), outputs.iter_mut()) { + pub fn build(value: Vec<OwnedMessage>) -> Result<Self> { + let mut outputs = Answers::alloc(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 { - Response::NoResponse => { - RawTextResponse::fill(output, "")?; - } - Response::Text(data) => { - RawTextResponse::fill(output, data)?; - } - Response::MaskedText(data) => { - RawTextResponse::fill(output, data.unsecure())?; - } - Response::Binary(data) => { - RawBinaryResponse::fill(output, data.data(), data.data_type())?; - } + 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 `*RawResponse` for passing to PAM. + /// Converts this into a `*Answer` for passing to PAM. /// /// The pointer "owns" its own data (i.e., this will not be dropped). - pub fn into_ptr(self) -> *mut RawResponse { + pub fn into_ptr(self) -> *mut Answer { let ret = self.base; mem::forget(self); ret } - /// Takes ownership of a list of responses allocated on the C heap. + /// 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. - pub unsafe fn from_c_heap(base: *mut RawResponse, count: usize) -> Self { - OwnedResponses { base, count } + pub unsafe fn from_c_heap(base: *mut Answer, count: usize) -> Self { + Answers { base, count } } } -#[derive(Debug, thiserror::Error)] -#[error("error converting responses: {0}")] -pub enum FillError { - NulError(#[from] NulError), - TooBigError(#[from] TooBigError), -} - -impl Deref for OwnedResponses { - type Target = [RawResponse]; +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 OwnedResponses { +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 OwnedResponses { +impl Drop for Answers { fn drop(&mut self) { // SAFETY: We allocated this ourselves, or it was provided to us by PAM. unsafe { - for resp in self.iter_mut() { - libc::free(resp.data) + for answer in self.iter_mut() { + answer.free_contents() } libc::free(self.base.cast()) } @@ -242,76 +233,95 @@ #[cfg(test)] mod tests { - use super::{BinaryData, OwnedResponses, RawBinaryResponse, RawTextResponse, Response}; + use super::{Answers, BinaryAnswer, TextAnswer, BorrowedBinaryData}; + use crate::BinaryData; + use crate::conv::{BinaryQAndA, ErrorMsg, InfoMsg, MaskedQAndA, QAndA, RadioQAndA}; + use crate::libpam::conversation::OwnedMessage; #[test] fn test_round_trip() { - let responses = [ - Response::Binary(BinaryData::new(vec![1, 2, 3], 99)), - Response::Text("whats going on".to_owned()), - Response::MaskedText("well then".into()), - Response::NoResponse, - Response::Text("bogus".to_owned()), + 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 sent = OwnedResponses::build(&responses).unwrap(); - let heap_resps = sent.into_ptr(); - let mut received = unsafe { OwnedResponses::from_c_heap(heap_resps, 5) }; + 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 { RawTextResponse::upcast(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_data: &[u8], want_type, raw| { - let up = unsafe { RawBinaryResponse::upcast(raw) }; - assert_eq!(want_data, up.data()); - assert_eq!(want_type, up.data_type()); - up.free_contents(); - let empty: [u8; 0] = []; - assert_eq!(&empty, up.data()); - assert_eq!(0, up.data_type()); + let up = unsafe { BinaryAnswer::upcast(raw) }; + assert_eq!(BinaryData::new(want_data.into(), want_type), up.data().into()); + up.zero_contents(); + assert_eq!(BinaryData::default(), up.data().into()); }; - if let [zero, one, two, three, four] = &mut received[..] { + 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("bogus", four); + assert_text("", four); + assert_text("beep boop", five); } else { - panic!("wrong size!") + panic!("received wrong size {len}!", len = received.len()) } } #[test] - fn test_text_response() { - let mut responses = OwnedResponses::alloc(2); - let text = RawTextResponse::fill(&mut responses[0], "hello").unwrap(); - let data = text.contents().expect("valid"); + fn test_text_answer() { + let mut answers = Answers::alloc(2); + let zeroth = &mut answers[0]; + TextAnswer::fill(zeroth, "hello").unwrap(); + let zeroth_text = unsafe { TextAnswer::upcast(zeroth) }; + let data = zeroth_text.contents().expect("valid"); assert_eq!("hello", data); - text.free_contents(); - text.free_contents(); - RawTextResponse::fill(&mut responses[1], "hell\0").expect_err("should error; contains nul"); + zeroth_text.free_contents(); + zeroth_text.free_contents(); + TextAnswer::fill(&mut answers[1], "hell\0").expect_err("should error; contains nul"); } #[test] - fn test_binary_response() { - let mut responses = OwnedResponses::alloc(1); - let real_data = [1, 2, 3, 4, 5, 6, 7, 8]; - let resp = RawBinaryResponse::fill(&mut responses[0], &real_data, 7) - .expect("alloc should succeed"); - let data = resp.data(); - assert_eq!(&real_data, data); - assert_eq!(7, resp.data_type()); - resp.free_contents(); - resp.free_contents(); + fn test_binary_answer() { + let mut answers = Answers::alloc(1); + let real_data = BinaryData::new(vec![1, 2, 3, 4, 5, 6, 7, 8], 9); + let answer = &mut answers[0]; + 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(); } #[test] #[ignore] - fn test_binary_response_too_big() { + fn test_binary_answer_too_big() { let big_data: Vec<u8> = vec![0xFFu8; 10_000_000_000]; - let mut responses = OwnedResponses::alloc(1); - RawBinaryResponse::fill(&mut responses[0], &big_data, 0).expect_err("this is too big!"); + let mut answers = Answers::alloc(1); + BinaryAnswer::fill(&mut answers[0], BorrowedBinaryData::new(&big_data, 100)) + .expect_err("this is too big!"); } }