Mercurial > crates > nonstick
view src/pam_ffi/response.rs @ 72:47eb242a4f88
Fill out the PamHandle trait.
This updates the PamHandle trait to have methods for each Item,
and implements them on the LibPamHandle.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Wed, 04 Jun 2025 03:53:36 -0400 |
parents | 58f9d2a4df38 |
children | ac6881304c78 |
line wrap: on
line source
//! Types used when dealing with PAM conversations. use crate::pam_ffi::memory; use crate::pam_ffi::memory::{CBinaryData, Immovable, NulError, TooBigError}; use std::ffi::{c_char, c_int, c_void, CStr}; use std::ops::{Deref, DerefMut}; use std::result::Result as StdResult; use std::str::Utf8Error; use std::{mem, ptr, slice}; #[repr(transparent)] #[derive(Debug)] pub struct RawTextResponse(RawResponse); impl RawTextResponse { /// Allocates a new text response on the C heap. /// /// Both `self` and its internal pointer are located on the C heap. /// 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(); Ok(unsafe { &mut *(dest as *mut RawResponse as *mut Self) }) } /// Gets the string stored in this response. pub fn contents(&self) -> StdResult<&str, Utf8Error> { // 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 as *const c_char) }.to_str() } /// Releases memory owned by this response. /// /// # Safety /// /// You are responsible for no longer using this after calling free. pub unsafe fn free_contents(&mut self) { let data = self.0.data; memory::zero_c_string(data); libc::free(data); self.0.data = ptr::null_mut() } } /// A [`RawResponse`] with [`CBinaryData`] in it. #[repr(transparent)] #[derive(Debug)] pub struct RawBinaryResponse(RawResponse); impl RawBinaryResponse { /// Allocates a new binary response on the C heap. /// /// 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)? as *mut c_void; Ok(unsafe { (dest as *mut RawResponse) .cast::<RawBinaryResponse>() .as_mut() .unwrap() }) } /// Gets the binary data in this response. pub fn contents(&self) -> &[u8] { self.data().contents() } /// Gets the `data_type` tag that was embedded with the message. pub fn data_type(&self) -> u8 { self.data().data_type() } #[inline] fn data(&self) -> &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 as *const CBinaryData) } } /// Releases memory owned by this response. /// /// # Safety /// /// You are responsible for not using this after calling free. pub unsafe fn free_contents(&mut self) { let data_ref = (self.0.data as *mut 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)] #[repr(C)] 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>()) } as *mut RawResponse, count, } } /// 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. unsafe fn from_c_heap(base: *mut RawResponse, count: usize) -> Self { OwnedResponses { base, count } } } impl From<OwnedResponses> for *mut RawResponse { /// Converts this into a pointer to `RawResponse`. /// /// The backing data is no longer freed. fn from(value: OwnedResponses) -> Self { let ret = value.base; mem::forget(value); ret } } impl Deref for OwnedResponses { type Target = [RawResponse]; fn deref(&self) -> &Self::Target { // SAFETY: We allocated this ourselves, or it was provided to us by PAM. unsafe { slice::from_raw_parts(self.base, self.count) } } } impl DerefMut for OwnedResponses { fn deref_mut(&mut self) -> &mut Self::Target { // SAFETY: We allocated this ourselves, or it was provided to us by PAM. 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 as *mut c_void) } } } #[cfg(test)] mod tests { use super::{OwnedResponses, RawBinaryResponse, RawTextResponse}; #[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); unsafe { 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.contents(); assert_eq!(&real_data, data); assert_eq!(7, resp.data_type()); unsafe { 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!"); } }