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());
     }
 }