Mercurial > crates > nonstick
diff src/libpam/handle.rs @ 153:3036f2e6a022
Add module-specific data support.
This adds support for a safe form of `pam_get_data` and `pam_set_data`,
where data is (as best as humanly possible) type-safe and restricted
to only the module where it was created.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Tue, 08 Jul 2025 00:31:54 -0400 |
parents | 4b3a5095f68c |
children | ab8020566cd9 |
line wrap: on
line diff
--- a/src/libpam/handle.rs Mon Jul 07 19:05:31 2025 -0400 +++ b/src/libpam/handle.rs Tue Jul 08 00:31:54 2025 -0400 @@ -1,5 +1,5 @@ use super::conversation::{OwnedConversation, PamConv}; -use crate::_doc::{guide, linklist, stdlinks}; +use crate::_doc::{guide, linklist, man7, stdlinks}; use crate::constants::{ErrorCode, Result}; use crate::conv::Exchange; use crate::environ::EnvironMapMut; @@ -12,8 +12,9 @@ use crate::{Conversation, EnvironMap, Flags, ModuleClient, Transaction}; use libpam_sys_consts::constants; use num_enum::{IntoPrimitive, TryFromPrimitive}; +use std::any::TypeId; use std::cell::Cell; -use std::ffi::{c_char, c_int, CString, OsStr, OsString}; +use std::ffi::{c_char, c_int, c_void, CString, OsStr, OsString}; use std::mem::ManuallyDrop; use std::os::unix::ffi::OsStrExt; use std::ptr; @@ -107,17 +108,37 @@ }) } - /// "Quietly" closes the PAM session on an owned PAM handle. + #[cfg_attr( + pam_impl = "LinuxPam", + doc = "Ends the PAM transaction \"quietly\" (on Linux-PAM only)." + )] + #[cfg_attr( + not(pam_impl = "LinuxPam"), + doc = "Exactly equivalent to `drop(self)` (except on Linux-PAM)." + )] /// - /// This internally calls `pam_end` with the appropriate error code. + /// On Linux-PAM, this is equivalent to passing the `PAM_DATA_SILENT` flag + /// to [`pam_end` on Linux-PAM][man7], which signals that data cleanup + /// should "not treat the call too seriously" \[sic]. + /// + /// On other platforms, this is no different than letting the transaction + /// end on its own. /// - /// # References - #[doc = linklist!(pam_end: adg, _std)] - /// - #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")] - #[doc = stdlinks!(3 pam_end)] + #[doc = man7!(3 pam_end)] + pub fn end_silent(self) { + #[cfg(pam_impl = "LinuxPam")] + { + let mut me = ManuallyDrop::new(self); + me.end_internal(libpam_sys::PAM_DATA_SILENT); + } + // If it's not LinuxPam, we just drop normally. + } - fn end_quiet(self) {} + /// Internal "end" function, which binary-ORs the status with `or_with`. + fn end_internal(&mut self, or_with: i32) { + let result = ErrorCode::result_to_c(self.last_return.get()) | or_with; + unsafe { libpam_sys::pam_end(self.handle.raw_mut(), result) }; + } } macro_rules! wrap { @@ -150,12 +171,7 @@ #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")] #[doc = stdlinks!(3 pam_end)] fn drop(&mut self) { - unsafe { - libpam_sys::pam_end( - self.handle.raw_mut(), - ErrorCode::result_to_c(self.last_return.get()), - ); - } + self.end_internal(0) } } @@ -273,7 +289,7 @@ /// #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")] #[doc = stdlinks!(3 pam_end)] - pub fn end_quiet(self, result: Result<()>) { + pub fn end_silent(self, result: Result<()>) { let mut me = ManuallyDrop::new(self); let result = ErrorCode::result_to_c(result); #[cfg(pam_impl = "LinuxPam")] @@ -390,6 +406,37 @@ self.get_authtok(prompt, ItemType::OldAuthTok) } + fn get_module_data<T: 'static>(&self, key: &str) -> Option<&T> { + // It's technically unsafe to do this, but we assume that other modules + // aren't going to go out of their way to find the key we've used + // and corrupt its value's data. + let full_key = module_data_key::<T>(key); + let mut ptr: *const c_void = ptr::null(); + unsafe { + ErrorCode::result_from(libpam_sys::pam_get_data( + self.raw_ref(), + full_key.as_ptr(), + &mut ptr, + )) + .ok()?; + + (ptr as *const T).as_ref() + } + } + + fn set_module_data<T: 'static>(&mut self, key: &str, data: T) -> Result<()> { + let full_key = module_data_key::<T>(key); + let data = Box::new(data); + ErrorCode::result_from(unsafe { + libpam_sys::pam_set_data( + self.raw_mut(), + full_key.as_ptr(), + Box::into_raw(data).cast(), + drop_module_data::<T>, + ) + }) + } + fn authtok_item(&self) -> Result<Option<OsString>> { unsafe { items::get_cstr_item(self, ItemType::AuthTok) } } @@ -398,13 +445,27 @@ } } +/// Constructs a type-specific, module-specific key for this data. +fn module_data_key<T: 'static>(key: &str) -> CString { + // The type ID is unique per-type. + let tid = TypeId::of::<T>(); + // The `set_data_cleanup` function lives statically inside each PAM module, + // so its address will be different between `pam_a.so` and `pam_b.so`, + // even if both modules .so files are byte-for-byte identical. + let cleanup_addr = drop_module_data::<T> as usize; + // Then, by adding the key, + let key = format!("{key:?}::{tid:?}::{cleanup_addr:016x}"); + CString::new(key).expect("null bytes somehow got into a debug string?") +} + /// 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) { +extern "C" fn drop_module_data<T>(_: *mut libpam_sys::pam_handle, c_data: *mut c_void, _: c_int) { unsafe { - let _data: Box<T> = Box::from_raw(c_data.cast()); + // Adopt the pointer into a Box and immediately drop it. + let _: Box<T> = Box::from_raw(c_data.cast()); } }