Mercurial > crates > nonstick
view src/pam_ffi/message.rs @ 71:58f9d2a4df38
Reorganize everything again???
- Splits ffi/memory stuff into a bunch of stuff in the pam_ffi module.
- Builds infrastructure for passing Messages and Responses.
- Adds tests for some things at least.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Tue, 03 Jun 2025 21:54:58 -0400 |
parents | src/pam_ffi.rs@9f8381a1c09c |
children | 47eb242a4f88 |
line wrap: on
line source
//! Data and types dealing with PAM messages. use crate::constants::InvalidEnum; use crate::pam_ffi::memory; use crate::pam_ffi::memory::{CBinaryData, NulError, TooBigError}; use num_derive::FromPrimitive; use num_traits::FromPrimitive; use std::ffi::{c_char, c_int, c_void, CStr}; use std::result::Result as StdResult; use std::slice; use std::str::Utf8Error; /// The types of message and request that can be sent to a user. /// /// The data within each enum value is the prompt (or other information) /// that will be presented to the user. #[derive(Debug)] pub enum Message<'a> { /// Requests information from the user; will be masked when typing. /// /// Response: [`Response::MaskedText`] MaskedPrompt(&'a str), /// Requests information from the user; will not be masked. /// /// Response: [`Response::Text`] Prompt(&'a str), /// "Yes/No/Maybe conditionals" (a Linux-PAM extension). /// /// Response: [`Response::Text`] /// (Linux-PAM documentation doesn't define its contents.) RadioPrompt(&'a str), /// Raises an error message to the user. /// /// Response: [`Response::NoResponse`] Error(&'a str), /// Sends an informational message to the user. /// /// Response: [`Response::NoResponse`] Info(&'a str), /// Requests binary data from the client (a Linux-PAM extension). /// /// This is used for non-human or non-keyboard prompts (security key?). /// NOT part of the X/Open PAM specification. /// /// Response: [`Response::Binary`] BinaryPrompt { /// Some binary data. data: &'a [u8], /// A "type" that you can use for signalling. Has no strict definition in PAM. data_type: u8, }, } impl Message<'_> { /// Copies the contents of this message to the C heap. fn copy_to_heap(&self) -> StdResult<(Style, *mut c_void), ConversionError> { let alloc = |style, text| Ok((style, memory::malloc_str(text)?.cast())); match *self { Self::MaskedPrompt(text) => alloc(Style::PromptEchoOff, text), Self::Prompt(text) => alloc(Style::PromptEchoOn, text), Self::RadioPrompt(text) => alloc(Style::RadioType, text), Self::Error(text) => alloc(Style::ErrorMsg, text), Self::Info(text) => alloc(Style::TextInfo, text), Self::BinaryPrompt { data, data_type } => Ok(( Style::BinaryPrompt, (CBinaryData::alloc(data, data_type)?).cast(), )), } } } #[derive(Debug, thiserror::Error)] #[error("error creating PAM message: {0}")] enum ConversionError { InvalidEnum(#[from] InvalidEnum<Style>), Utf8Error(#[from] Utf8Error), NulError(#[from] NulError), TooBigError(#[from] TooBigError), } /// The C enum values for messages shown to the user. #[derive(Debug, PartialEq, FromPrimitive)] pub enum Style { /// 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. A Linux-PAM extension. RadioType = 5, /// For server–client non-human interaction. /// /// NOT part of the X/Open PAM specification. /// A Linux-PAM extension. BinaryPrompt = 7, } impl TryFrom<c_int> for Style { type Error = InvalidEnum<Self>; fn try_from(value: c_int) -> StdResult<Self, Self::Error> { Self::from_i32(value).ok_or(value.into()) } } impl From<Style> for c_int { fn from(val: Style) -> Self { val as Self } } /// A message sent by PAM or a module to an application. /// This message, and its internal data, is owned by the creator /// (either the module or PAM itself). #[repr(C)] pub struct RawMessage { /// The style of message to request. style: c_int, /// A description of the data requested. /// /// For most requests, this will be an owned [`CStr`], but for requests /// with [`Style::BinaryPrompt`], this will be [`BinaryData`] /// (a Linux-PAM extension). data: *mut c_void, } impl RawMessage { fn set(&mut self, msg: &Message) -> StdResult<(), ConversionError> { let (style, data) = msg.copy_to_heap()?; self.clear(); // SAFETY: We allocated this ourselves or were given it by PAM. // Otherwise, it's null, but free(null) is fine. unsafe { libc::free(self.data) }; self.style = style as c_int; self.data = data; Ok(()) } /// Retrieves the data stored in this message. fn data(&self) -> StdResult<Message, ConversionError> { let style: Style = self.style.try_into()?; // SAFETY: We either allocated this message ourselves or were provided it by PAM. let result = unsafe { match style { Style::PromptEchoOff => Message::MaskedPrompt(self.string_data()?), Style::PromptEchoOn => Message::Prompt(self.string_data()?), Style::TextInfo => Message::Info(self.string_data()?), Style::ErrorMsg => Message::Error(self.string_data()?), Style::RadioType => Message::Error(self.string_data()?), Style::BinaryPrompt => (self.data as *const CBinaryData).as_ref().map_or_else( || Message::BinaryPrompt { data_type: 0, data: &[], }, |data| Message::BinaryPrompt { data_type: data.data_type(), data: data.contents(), }, ), } }; Ok(result) } /// Gets this message's data pointer as a string. /// /// # Safety /// /// It's up to you to pass this only on types with a string value. unsafe fn string_data(&self) -> StdResult<&str, Utf8Error> { if self.data.is_null() { Ok("") } else { CStr::from_ptr(self.data as *const c_char).to_str() } } /// Zeroes out the data stored here. fn clear(&mut self) { // SAFETY: We either created this data or we got it from PAM. // After this function is done, it will be zeroed out. unsafe { if let Ok(style) = Style::try_from(self.style) { match style { Style::BinaryPrompt => { if let Some(d) = (self.data as *mut CBinaryData).as_mut() { d.zero_contents() } } Style::TextInfo | Style::RadioType | Style::ErrorMsg | Style::PromptEchoOff | Style::PromptEchoOn => memory::zero_c_string(self.data), } }; } } } /// Abstraction of a list-of-messages to be sent in a PAM conversation. /// /// On Linux-PAM and other compatible implementations, `messages` /// is treated as a pointer-to-pointers, like `int argc, char **argv`. /// (In this situation, the value of `OwnedMessages.indirect` is /// the pointer passed to `pam_conv`.) /// /// ```text /// ╔═ OwnedMsgs ═╗ points to ┌─ Indirect ─┐ ╔═ Message ═╗ /// ║ indirect ┄┄┄╫┄┄┄┄┄┄┄┄┄┄┄> │ base[0] ┄┄┄┼┄┄┄┄┄> ║ style ║ /// ║ count ║ │ base[1] ┄┄┄┼┄┄┄╮ ║ data ║ /// ╚═════════════╝ │ ... │ ┆ ╚═══════════╝ /// ┆ /// ┆ ╔═ Message ═╗ /// ╰┄┄> ║ style ║ /// ║ data ║ /// ╚═══════════╝ /// ``` /// /// On OpenPAM and other compatible implementations (like Solaris), /// `messages` is a pointer-to-pointer-to-array. This appears to be /// the correct implementation as required by the XSSO specification. /// /// ```text /// ╔═ OwnedMsgs ═╗ points to ┌─ Indirect ─┐ ╔═ Message[] ═╗ /// ║ indirect ┄┄┄╫┄┄┄┄┄┄┄┄┄┄┄> │ base ┄┄┄┄┄┄┼┄┄┄┄┄> ║ style ║ /// ║ count ║ └────────────┘ ║ data ║ /// ╚═════════════╝ ╟─────────────╢ /// ║ style ║ /// ║ data ║ /// ╟─────────────╢ /// ║ ... ║ /// ``` /// /// ***THIS LIBRARY CURRENTLY SUPPORTS ONLY LINUX-PAM.*** #[repr(C)] pub struct OwnedMessages { /// An indirection to the messages themselves, stored on the C heap. indirect: *mut Indirect<RawMessage>, /// The number of messages in the list. count: usize, } impl OwnedMessages { /// Allocates data to store messages on the C heap. pub fn alloc(count: usize) -> Self { // SAFETY: We're allocating. That's safe. unsafe { // Since this is Linux-PAM, the indirect is a list of pointers. let indirect = libc::calloc(count, size_of::<Indirect<RawMessage>>()) as *mut Indirect<RawMessage>; let indir_ptrs = slice::from_raw_parts_mut(indirect, count); for ptr in indir_ptrs { ptr.base = libc::calloc(1, size_of::<RawMessage>()) as *mut RawMessage; } Self { indirect, count } } } /// Gets a reference to the message at the given index. pub fn get(&self, index: usize) -> Option<&RawMessage> { (index < self.count).then(|| unsafe { (*self.indirect).at(index) }) } /// Gets a mutable reference to the message at the given index. pub fn get_mut(&mut self, index: usize) -> Option<&mut RawMessage> { (index < self.count).then(|| unsafe { (*self.indirect).at_mut(index) }) } } #[repr(transparent)] struct Indirect<T> { /// The starting address for the T. base: *mut T, } impl<T> Indirect<T> { /// Gets a mutable reference to the element at the given index. /// /// # Safety /// /// We don't check `index`. unsafe fn at_mut(&mut self, index: usize) -> &mut T { &mut *self.base.add(index) } unsafe fn at(&self, index: usize) -> &T { &*self.base.add(index) } }