Mercurial > crates > nonstick
diff 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 diff
--- a/src/conv.rs Tue May 27 16:40:49 2025 -0400 +++ b/src/conv.rs Sun Jun 01 01:15:04 2025 -0400 @@ -1,123 +1,81 @@ -//! The [Conversation] struct, for interacting with the user. -//! -//! This module is experimental and will probably be rewritten in the future -//! to improve the interface for both PAM modules and clients. +//! The PAM conversation and associated Stuff. -use crate::constants::MessageStyle; -use crate::constants::Result; -use crate::constants::{ErrorCode, InvalidEnum}; -use crate::items::Item; -use libc::{c_char, c_int}; -use num_derive::FromPrimitive; -use std::ffi::{CStr, CString}; -use std::ptr; +use crate::pam_ffi::{BinaryResponseInner, NulError, TextResponseInner}; +use std::num::TryFromIntError; +use std::ops::Deref; +use std::result::Result as StdResult; -/// Styles of message that are shown to the user. -#[derive(Debug, PartialEq, FromPrimitive)] -#[non_exhaustive] // non-exhaustive because C might give us back anything! -pub enum MessageStyle { - /// Requests information from the user; will be masked when typing. - PromptEchoOff = 1, - /// Requests information from the user; will not be masked. - PromptEchoOn = 2, - /// An error message. - ErrorMsg = 3, - /// An informational message. - TextInfo = 4, - /// Yes/No/Maybe conditionals. Linux-PAM specific. - RadioType = 5, - /// For server–client non-human interaction. - /// NOT part of the X/Open PAM specification. - BinaryPrompt = 7, -} +/// An owned text response to a PAM conversation. +/// +/// It points to a value on the C heap. +#[repr(C)] +struct TextResponse(*mut TextResponseInner); -impl TryFrom<c_int> for MessageStyle { - type Error = InvalidEnum<Self>; - fn try_from(value: c_int) -> std::result::Result<Self, Self::Error> { - Self::from_i32(value).ok_or(value.into()) +impl TextResponse { + /// Creates a text response. + pub fn new(text: impl AsRef<str>) -> StdResult<Self, NulError> { + TextResponseInner::alloc(text).map(Self) } } -impl From<MessageStyle> for c_int { - fn from(val: MessageStyle) -> Self { - val as 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) } } } -#[repr(C)] -struct Message { - msg_style: MessageStyle, - msg: *const c_char, -} - +/// An owned binary response to a PAM conversation. +/// +/// It points to a value on the C heap. #[repr(C)] -struct Response { - resp: *const c_char, - resp_retcode: libc::c_int, // Unused - always zero -} +struct BinaryResponse(*mut BinaryResponseInner); -#[doc(hidden)] -#[repr(C)] -pub struct Inner { - conv: extern "C" fn( - num_msg: c_int, - pam_message: &&Message, - pam_response: &mut *const Response, - appdata_ptr: *const libc::c_void, - ) -> c_int, - appdata_ptr: *const libc::c_void, +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) + } } -/// A communication channel with the user. -/// -/// Use this to communicate with the user, if needed, beyond the standard -/// things you can get/set with `get_user`/`get_authtok` and friends. -/// The PAM client (i.e., the application that is logging in) will present -/// the messages you send to the user and ask for responses. -pub struct Conversation<'a>(&'a Inner); - -impl Conversation<'_> { - /// Sends a message to the PAM client. - /// - /// This will typically result in the user seeing a message or a prompt. - /// For details, see what [MessageStyle]s are available. - /// - /// Note that the user experience will depend on how each style - /// is implemented by the client, and that not all clients - /// will implement all message styles. - pub fn send(&self, style: MessageStyle, msg: &str) -> Result<Option<&CStr>> { - let mut resp_ptr: *const Response = ptr::null(); - let msg_cstr = CString::new(msg).unwrap(); - let msg = Message { - msg_style: style, - msg: msg_cstr.as_ptr(), - }; - // TODO: These need to be freed! - let ret = (self.0.conv)(1, &&msg, &mut resp_ptr, self.0.appdata_ptr); - ErrorCode::result_from(ret)?; - - let result = unsafe { - match (*resp_ptr).resp { - p if p.is_null() => None, - p => Some(CStr::from_ptr(p)), - } - }; - Ok(result) +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 Item for Conversation<'_> { - type Raw = Inner; - - fn type_id() -> crate::items::ItemType { - crate::items::ItemType::Conversation - } - - unsafe fn from_raw(raw: *const Self::Raw) -> Self { - Self(&*raw) - } - - fn into_raw(self) -> *const Self::Raw { - self.0 as _ +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()); + } +}