view src/conv.rs @ 69:8f3ae0c7ab92

Rework conversation data types and make safe wrappers. This removes the old `Conversation` type and reworks the FFI types used for PAM conversations. This creates safe `TestResponse` and `BinaryResponse` structures in `conv`, providing a safe way to pass response messages to PAM Conversations. The internals of these types are allocated on the C heap, as required by PAM. We also remove the Conversation struct, which was specific to the real PAM implementation so that we can introduce a better abstraction. Also splits a new `PamApplicationHandle` trait from `PamHandle`, for the parts of a PAM handle that are specific to the application side of a PAM transaction.
author Paul Fisher <paul@pfish.zone>
date Sun, 01 Jun 2025 01:15:04 -0400
parents bbe84835d6db
children 9f8381a1c09c
line wrap: on
line source

//! The PAM conversation and associated Stuff.

use crate::pam_ffi::{BinaryResponseInner, NulError, TextResponseInner};
use std::num::TryFromIntError;
use std::ops::Deref;
use std::result::Result as StdResult;

/// An owned text response to a PAM conversation.
///
/// It points to a value on the C heap.
#[repr(C)]
struct TextResponse(*mut TextResponseInner);

impl TextResponse {
    /// Creates a text response.
    pub fn new(text: impl AsRef<str>) -> StdResult<Self, NulError> {
        TextResponseInner::alloc(text).map(Self)
    }
}

impl Deref for TextResponse {
    type Target = TextResponseInner;
    fn deref(&self) -> &Self::Target {
        // SAFETY: We allocated this ourselves, or it was provided by PAM.
        unsafe { &*self.0 }
    }
}

impl Drop for TextResponse {
    /// Frees an owned response.
    fn drop(&mut self) {
        // SAFETY: We allocated this ourselves, or it was provided by PAM.
        unsafe { TextResponseInner::free(self.0) }
    }
}

/// An owned binary response to a PAM conversation.
///
/// It points to a value on the C heap.
#[repr(C)]
struct BinaryResponse(*mut BinaryResponseInner);

impl BinaryResponse {
    /// Creates a binary response with the given data.
    pub fn new(data: impl AsRef<[u8]>, data_type: u8) -> StdResult<Self, TryFromIntError> {
        BinaryResponseInner::alloc(data, data_type).map(Self)
    }
}

impl Deref for BinaryResponse {
    type Target = BinaryResponseInner;
    fn deref(&self) -> &Self::Target {
        // SAFETY: We allocated this ourselves, or it was provided by PAM.
        unsafe { &*self.0 }
    }
}

impl Drop for BinaryResponse {
    /// Frees an owned response.
    fn drop(&mut self) {
        // SAFETY: We allocated this ourselves, or it was provided by PAM.
        unsafe { BinaryResponseInner::free(self.0) }
    }
}

#[cfg(test)]
mod test {
    use super::{BinaryResponse, TextResponse};

    #[test]
    fn test_text_response() {
        let resp = TextResponse::new("it's a-me!").unwrap();
        assert_eq!("it's a-me!", resp.contents().to_str().unwrap());
    }
    #[test]
    fn test_binary_response() {
        let data = [123, 210, 55];
        let resp = BinaryResponse::new(&data, 99).unwrap();
        assert_eq!(&data, resp.contents());
    }
}