Mercurial > crates > nonstick
view src/pam_ffi/handle.rs @ 74:c7c596e6388f
Make conversations type-safe (last big reorg) (REAL) (NOT CLICKBAIT)
In previous versions of Conversation, you could send messages and then
return messages of the wrong type or in the wrong order or whatever.
The receiver would then have to make sure that there were the right
number of messages and that each message was the right type.
That's annoying.
This change makes the `Message` enum a two-way channel, where the asker
puts their question into it, and then the answerer (the conversation)
puts the answer in and returns control to the asker. The asker then
only has to pull the Answer of the type they wanted out of the message.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Fri, 06 Jun 2025 22:21:17 -0400 |
parents | ac6881304c78 |
children |
line wrap: on
line source
use super::conversation::LibPamConversation; use crate::constants::{ErrorCode, InvalidEnum, Result}; use crate::conv::Message; use crate::handle::{PamApplicationOnly, PamModuleOnly, PamShared}; use crate::pam_ffi::memory; use crate::pam_ffi::memory::Immovable; use crate::{Conversation, Response}; use num_derive::FromPrimitive; use num_traits::FromPrimitive; use std::ffi::{c_char, c_int}; use std::ops::{Deref, DerefMut}; use std::result::Result as StdResult; use std::{mem, ptr}; /// An owned PAM handle. #[repr(transparent)] pub struct OwnedLibPamHandle(*mut LibPamHandle); /// An opaque structure that a PAM handle points to. #[repr(C)] pub struct LibPamHandle { _data: (), _marker: Immovable, } impl LibPamHandle { /// Gets a C string item. /// /// # Safety /// /// You better be requesting an item which is a C string. unsafe fn get_cstr_item(&mut self, item_type: ItemType) -> Result<Option<&str>> { let mut output = ptr::null(); let ret = unsafe { super::pam_get_item(self, item_type as c_int, &mut output) }; ErrorCode::result_from(ret)?; memory::wrap_string(output.cast()) } /// Sets a C string item. /// /// # Safety /// /// You better be setting an item which is a C string. unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&str>) -> Result<()> { let data_str = memory::option_cstr(data)?; let ret = unsafe { super::pam_set_item( self, item_type as c_int, memory::prompt_ptr(data_str.as_ref()).cast(), ) }; ErrorCode::result_from(ret) } /// Gets the `PAM_CONV` item from the handle. fn conversation_item(&mut self) -> Result<&mut LibPamConversation> { let output: *mut LibPamConversation = ptr::null_mut(); let result = unsafe { super::pam_get_item( self, ItemType::Conversation.into(), &mut output.cast_const().cast(), ) }; ErrorCode::result_from(result)?; // SAFETY: We got this result from PAM, and we're checking if it's null. unsafe { output.as_mut() }.ok_or(ErrorCode::ConversationError) } } impl PamApplicationOnly for OwnedLibPamHandle { fn close(self, status: Result<()>) -> Result<()> { let ret = unsafe { super::pam_end(self.0, ErrorCode::result_to_c(status)) }; // Forget rather than dropping, since dropping also calls pam_end. mem::forget(self); ErrorCode::result_from(ret) } } impl Deref for OwnedLibPamHandle { type Target = LibPamHandle; fn deref(&self) -> &Self::Target { unsafe { &*self.0 } } } impl DerefMut for OwnedLibPamHandle { fn deref_mut(&mut self) -> &mut Self::Target { unsafe { &mut *self.0 } } } impl Drop for OwnedLibPamHandle { /// Ends the PAM session with a zero error code. /// You probably want to call [`close`](Self::close) instead of /// letting this drop by itself. fn drop(&mut self) { unsafe { super::pam_end(self.0, 0); } } } macro_rules! cstr_item { (get = $getter:ident, item = $item_type:path) => { fn $getter(&mut self) -> Result<Option<&str>> { unsafe { self.get_cstr_item($item_type) } } }; (set = $setter:ident, item = $item_type:path) => { fn $setter(&mut self, value: Option<&str>) -> Result<()> { unsafe { self.set_cstr_item($item_type, value) } } }; } impl PamShared for LibPamHandle { fn get_user(&mut self, prompt: Option<&str>) -> Result<&str> { let prompt = memory::option_cstr(prompt)?; let mut output: *const c_char = ptr::null(); let ret = unsafe { super::pam_get_user(self, &mut output, memory::prompt_ptr(prompt.as_ref())) }; ErrorCode::result_from(ret)?; unsafe { memory::wrap_string(output) } .transpose() .unwrap_or(Err(ErrorCode::ConversationError)) } cstr_item!(get = user_item, item = ItemType::User); cstr_item!(set = set_user_item, item = ItemType::User); cstr_item!(get = service, item = ItemType::Service); cstr_item!(set = set_service, item = ItemType::Service); cstr_item!(get = user_prompt, item = ItemType::UserPrompt); cstr_item!(set = set_user_prompt, item = ItemType::UserPrompt); cstr_item!(get = tty_name, item = ItemType::Tty); cstr_item!(set = set_tty_name, item = ItemType::Tty); cstr_item!(get = remote_user, item = ItemType::RemoteUser); cstr_item!(set = set_remote_user, item = ItemType::RemoteUser); cstr_item!(get = remote_host, item = ItemType::RemoteHost); cstr_item!(set = set_remote_host, item = ItemType::RemoteHost); cstr_item!(set = set_authtok_item, item = ItemType::AuthTok); cstr_item!(set = set_old_authtok_item, item = ItemType::OldAuthTok); } impl Conversation for LibPamHandle { fn communicate(&mut self, messages: &[Message]) -> Result<Vec<Response>> { self.conversation_item()?.communicate(messages) } } impl PamModuleOnly for LibPamHandle { fn get_authtok(&mut self, prompt: Option<&str>) -> Result<&str> { let prompt = memory::option_cstr(prompt)?; let mut output: *const c_char = ptr::null_mut(); // SAFETY: We're calling this with known-good values. let res = unsafe { super::pam_get_authtok( self, ItemType::AuthTok.into(), &mut output, memory::prompt_ptr(prompt.as_ref()), ) }; ErrorCode::result_from(res)?; // SAFETY: We got this string from PAM. unsafe { memory::wrap_string(output) } .transpose() .unwrap_or(Err(ErrorCode::ConversationError)) } cstr_item!(get = authtok_item, item = ItemType::AuthTok); cstr_item!(get = old_authtok_item, item = ItemType::OldAuthTok); } /// Function called at the end of a PAM session that is called to clean up /// a value previously provided to PAM in a `pam_set_data` call. /// /// You should never call this yourself. extern "C" fn set_data_cleanup<T>(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) { unsafe { let _data: Box<T> = Box::from_raw(c_data.cast()); } } /// Identifies what is being gotten or set with `pam_get_item` /// or `pam_set_item`. #[derive(FromPrimitive)] #[repr(i32)] #[non_exhaustive] // because C could give us anything! pub enum ItemType { /// The PAM service name. Service = 1, /// The user's login name. User = 2, /// The TTY name. Tty = 3, /// The remote host (if applicable). RemoteHost = 4, /// The conversation struct (not a CStr-based item). Conversation = 5, /// The authentication token (password). AuthTok = 6, /// The old authentication token (when changing passwords). OldAuthTok = 7, /// The remote user's name. RemoteUser = 8, /// The prompt shown when requesting a username. UserPrompt = 9, /// App-supplied function to override failure delays. FailDelay = 10, /// X display name. XDisplay = 11, /// X server authentication data. XAuthData = 12, /// The type of `pam_get_authtok`. AuthTokType = 13, } impl TryFrom<c_int> for ItemType { type Error = InvalidEnum<Self>; fn try_from(value: c_int) -> StdResult<Self, Self::Error> { Self::from_i32(value).ok_or(value.into()) } } impl From<ItemType> for c_int { fn from(val: ItemType) -> Self { val as Self } }