Mercurial > crates > nonstick
view src/libpam/response.rs @ 75:c30811b4afae
rename pam_ffi submodule to libpam.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Fri, 06 Jun 2025 22:35:08 -0400 |
parents | src/pam_ffi/response.rs@ac6881304c78 |
children | 351bdc13005e |
line wrap: on
line source
//! Types used when dealing with PAM conversations. use crate::conv::BinaryData; use crate::libpam::memory; use crate::libpam::memory::{CBinaryData, Immovable, NulError, TooBigError}; use crate::Response; 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); impl RawTextResponse { /// Interprets the provided `RawResponse` as a text response. /// /// # Safety /// /// It's up to you to provide a response that is a `RawTextResponse`. pub unsafe fn upcast(from: &mut RawResponse) -> &mut Self { // SAFETY: We're provided a valid reference. &mut *(from as *mut RawResponse).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) }) } /// Gets the string stored in this response. pub fn contents(&self) -> StdResult<&str, Utf8Error> { 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. // In either case, it's going to be a valid null-terminated string. unsafe { CStr::from_ptr(self.0.data.cast()) }.to_str() } } /// Releases memory owned by this response. pub fn free_contents(&mut self) { // SAFETY: We know we own this data. // After we're done, it will be null. unsafe { memory::zero_c_string(self.0.data); libc::free(self.0.data); self.0.data = ptr::null_mut() } } } /// A [`RawResponse`] with [`CBinaryData`] in it. #[repr(transparent)] #[derive(Debug)] pub struct RawBinaryResponse(RawResponse); impl RawBinaryResponse { /// Interprets the provided `RawResponse` as a binary response. /// /// # Safety /// /// It's up to you to provide a response that is a `RawBinaryResponse`. pub unsafe fn upcast(from: &mut RawResponse) -> &mut Self { // SAFETY: We're provided a valid reference. &mut *(from as *mut RawResponse).cast::<Self>() } /// Fills in a `RawResponse` 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) }) } /// 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. 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) { // SAFETY: We know that our data pointer is either valid or null. // Once we're done, it's null and the response is safe. unsafe { let data_ref = self.0.data.cast::<CBinaryData>().as_mut(); if let Some(d) = data_ref { d.zero_contents() } libc::free(self.0.data); self.0.data = ptr::null_mut() } } } /// Generic version of response data. /// /// This has the same structure as [`RawBinaryResponse`] /// and [`RawTextResponse`]. #[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 /// [`MessageStyle::BinaryPrompt`]s, this will be [`CBinaryData`] /// (a Linux-PAM extension). data: *mut c_void, /// Unused. return_code: c_int, _marker: Immovable, } /// A contiguous block of responses. #[derive(Debug)] pub struct OwnedResponses { base: *mut RawResponse, count: usize, } impl OwnedResponses { /// Allocates an owned list of responses on the C heap. fn alloc(count: usize) -> Self { OwnedResponses { // SAFETY: We are doing allocation here. base: unsafe { libc::calloc(count, size_of::<RawResponse>()) }.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()) { 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())?; } } } Ok(outputs) } /// Converts this into a `*RawResponse` for passing to PAM. /// /// The pointer "owns" its own data (i.e., this will not be dropped). pub fn into_ptr(self) -> *mut RawResponse { let ret = self.base; mem::forget(self); ret } /// Takes ownership of a list of responses 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 } } } #[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]; 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 { 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 { 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) } libc::free(self.base.cast()) } } } #[cfg(test)] mod tests { use super::{BinaryData, OwnedResponses, RawBinaryResponse, RawTextResponse, Response}; #[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 sent = OwnedResponses::build(&responses).unwrap(); let heap_resps = sent.into_ptr(); let mut received = unsafe { OwnedResponses::from_c_heap(heap_resps, 5) }; let assert_text = |want, raw| { let up = unsafe { RawTextResponse::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()); }; if let [zero, one, two, three, four] = &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); } else { panic!("wrong size!") } } #[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"); assert_eq!("hello", data); text.free_contents(); text.free_contents(); RawTextResponse::fill(&mut responses[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(); } #[test] #[ignore] fn test_binary_response_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!"); } }