comparison 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
comparison
equal deleted inserted replaced
152:4d11b2e7da83 153:3036f2e6a022
1 use super::conversation::{OwnedConversation, PamConv}; 1 use super::conversation::{OwnedConversation, PamConv};
2 use crate::_doc::{guide, linklist, stdlinks}; 2 use crate::_doc::{guide, linklist, man7, stdlinks};
3 use crate::constants::{ErrorCode, Result}; 3 use crate::constants::{ErrorCode, Result};
4 use crate::conv::Exchange; 4 use crate::conv::Exchange;
5 use crate::environ::EnvironMapMut; 5 use crate::environ::EnvironMapMut;
6 use crate::handle::PamShared; 6 use crate::handle::PamShared;
7 use crate::items::{Items, ItemsMut}; 7 use crate::items::{Items, ItemsMut};
10 use crate::libpam::{items, memory}; 10 use crate::libpam::{items, memory};
11 use crate::logging::{Level, Location}; 11 use crate::logging::{Level, Location};
12 use crate::{Conversation, EnvironMap, Flags, ModuleClient, Transaction}; 12 use crate::{Conversation, EnvironMap, Flags, ModuleClient, Transaction};
13 use libpam_sys_consts::constants; 13 use libpam_sys_consts::constants;
14 use num_enum::{IntoPrimitive, TryFromPrimitive}; 14 use num_enum::{IntoPrimitive, TryFromPrimitive};
15 use std::any::TypeId;
15 use std::cell::Cell; 16 use std::cell::Cell;
16 use std::ffi::{c_char, c_int, CString, OsStr, OsString}; 17 use std::ffi::{c_char, c_int, c_void, CString, OsStr, OsString};
17 use std::mem::ManuallyDrop; 18 use std::mem::ManuallyDrop;
18 use std::os::unix::ffi::OsStrExt; 19 use std::os::unix::ffi::OsStrExt;
19 use std::ptr; 20 use std::ptr;
20 use std::ptr::NonNull; 21 use std::ptr::NonNull;
21 22
105 last_return: Cell::new(Ok(())), 106 last_return: Cell::new(Ok(())),
106 conversation: conv, 107 conversation: conv,
107 }) 108 })
108 } 109 }
109 110
110 /// "Quietly" closes the PAM session on an owned PAM handle. 111 #[cfg_attr(
111 /// 112 pam_impl = "LinuxPam",
112 /// This internally calls `pam_end` with the appropriate error code. 113 doc = "Ends the PAM transaction \"quietly\" (on Linux-PAM only)."
113 /// 114 )]
114 /// # References 115 #[cfg_attr(
115 #[doc = linklist!(pam_end: adg, _std)] 116 not(pam_impl = "LinuxPam"),
116 /// 117 doc = "Exactly equivalent to `drop(self)` (except on Linux-PAM)."
117 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")] 118 )]
118 #[doc = stdlinks!(3 pam_end)] 119 ///
119 120 /// On Linux-PAM, this is equivalent to passing the `PAM_DATA_SILENT` flag
120 fn end_quiet(self) {} 121 /// to [`pam_end` on Linux-PAM][man7], which signals that data cleanup
122 /// should "not treat the call too seriously" \[sic].
123 ///
124 /// On other platforms, this is no different than letting the transaction
125 /// end on its own.
126 ///
127 #[doc = man7!(3 pam_end)]
128 pub fn end_silent(self) {
129 #[cfg(pam_impl = "LinuxPam")]
130 {
131 let mut me = ManuallyDrop::new(self);
132 me.end_internal(libpam_sys::PAM_DATA_SILENT);
133 }
134 // If it's not LinuxPam, we just drop normally.
135 }
136
137 /// Internal "end" function, which binary-ORs the status with `or_with`.
138 fn end_internal(&mut self, or_with: i32) {
139 let result = ErrorCode::result_to_c(self.last_return.get()) | or_with;
140 unsafe { libpam_sys::pam_end(self.handle.raw_mut(), result) };
141 }
121 } 142 }
122 143
123 macro_rules! wrap { 144 macro_rules! wrap {
124 (fn $name:ident { $pam_func:ident }) => { 145 (fn $name:ident { $pam_func:ident }) => {
125 fn $name(&mut self, flags: Flags) -> Result<()> { 146 fn $name(&mut self, flags: Flags) -> Result<()> {
148 #[doc = linklist!(pam_end: adg, _std)] 169 #[doc = linklist!(pam_end: adg, _std)]
149 /// 170 ///
150 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")] 171 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")]
151 #[doc = stdlinks!(3 pam_end)] 172 #[doc = stdlinks!(3 pam_end)]
152 fn drop(&mut self) { 173 fn drop(&mut self) {
153 unsafe { 174 self.end_internal(0)
154 libpam_sys::pam_end(
155 self.handle.raw_mut(),
156 ErrorCode::result_to_c(self.last_return.get()),
157 );
158 }
159 } 175 }
160 } 176 }
161 177
162 macro_rules! delegate { 178 macro_rules! delegate {
163 // First have the kind that save the result after delegation. 179 // First have the kind that save the result after delegation.
271 /// # References 287 /// # References
272 #[doc = linklist!(pam_end: adg, _std)] 288 #[doc = linklist!(pam_end: adg, _std)]
273 /// 289 ///
274 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")] 290 #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")]
275 #[doc = stdlinks!(3 pam_end)] 291 #[doc = stdlinks!(3 pam_end)]
276 pub fn end_quiet(self, result: Result<()>) { 292 pub fn end_silent(self, result: Result<()>) {
277 let mut me = ManuallyDrop::new(self); 293 let mut me = ManuallyDrop::new(self);
278 let result = ErrorCode::result_to_c(result); 294 let result = ErrorCode::result_to_c(result);
279 #[cfg(pam_impl = "LinuxPam")] 295 #[cfg(pam_impl = "LinuxPam")]
280 let result = result | libpam_sys::PAM_DATA_SILENT; 296 let result = result | libpam_sys::PAM_DATA_SILENT;
281 unsafe { 297 unsafe {
388 404
389 fn old_authtok(&mut self, prompt: Option<&OsStr>) -> Result<OsString> { 405 fn old_authtok(&mut self, prompt: Option<&OsStr>) -> Result<OsString> {
390 self.get_authtok(prompt, ItemType::OldAuthTok) 406 self.get_authtok(prompt, ItemType::OldAuthTok)
391 } 407 }
392 408
409 fn get_module_data<T: 'static>(&self, key: &str) -> Option<&T> {
410 // It's technically unsafe to do this, but we assume that other modules
411 // aren't going to go out of their way to find the key we've used
412 // and corrupt its value's data.
413 let full_key = module_data_key::<T>(key);
414 let mut ptr: *const c_void = ptr::null();
415 unsafe {
416 ErrorCode::result_from(libpam_sys::pam_get_data(
417 self.raw_ref(),
418 full_key.as_ptr(),
419 &mut ptr,
420 ))
421 .ok()?;
422
423 (ptr as *const T).as_ref()
424 }
425 }
426
427 fn set_module_data<T: 'static>(&mut self, key: &str, data: T) -> Result<()> {
428 let full_key = module_data_key::<T>(key);
429 let data = Box::new(data);
430 ErrorCode::result_from(unsafe {
431 libpam_sys::pam_set_data(
432 self.raw_mut(),
433 full_key.as_ptr(),
434 Box::into_raw(data).cast(),
435 drop_module_data::<T>,
436 )
437 })
438 }
439
393 fn authtok_item(&self) -> Result<Option<OsString>> { 440 fn authtok_item(&self) -> Result<Option<OsString>> {
394 unsafe { items::get_cstr_item(self, ItemType::AuthTok) } 441 unsafe { items::get_cstr_item(self, ItemType::AuthTok) }
395 } 442 }
396 fn old_authtok_item(&self) -> Result<Option<OsString>> { 443 fn old_authtok_item(&self) -> Result<Option<OsString>> {
397 unsafe { items::get_cstr_item(self, ItemType::OldAuthTok) } 444 unsafe { items::get_cstr_item(self, ItemType::OldAuthTok) }
398 } 445 }
446 }
447
448 /// Constructs a type-specific, module-specific key for this data.
449 fn module_data_key<T: 'static>(key: &str) -> CString {
450 // The type ID is unique per-type.
451 let tid = TypeId::of::<T>();
452 // The `set_data_cleanup` function lives statically inside each PAM module,
453 // so its address will be different between `pam_a.so` and `pam_b.so`,
454 // even if both modules .so files are byte-for-byte identical.
455 let cleanup_addr = drop_module_data::<T> as usize;
456 // Then, by adding the key,
457 let key = format!("{key:?}::{tid:?}::{cleanup_addr:016x}");
458 CString::new(key).expect("null bytes somehow got into a debug string?")
399 } 459 }
400 460
401 /// Function called at the end of a PAM session that is called to clean up 461 /// Function called at the end of a PAM session that is called to clean up
402 /// a value previously provided to PAM in a `pam_set_data` call. 462 /// a value previously provided to PAM in a `pam_set_data` call.
403 /// 463 ///
404 /// You should never call this yourself. 464 /// You should never call this yourself.
405 extern "C" fn set_data_cleanup<T>(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) { 465 extern "C" fn drop_module_data<T>(_: *mut libpam_sys::pam_handle, c_data: *mut c_void, _: c_int) {
406 unsafe { 466 unsafe {
407 let _data: Box<T> = Box::from_raw(c_data.cast()); 467 // Adopt the pointer into a Box and immediately drop it.
468 let _: Box<T> = Box::from_raw(c_data.cast());
408 } 469 }
409 } 470 }
410 471
411 // Implementations of internal functions. 472 // Implementations of internal functions.
412 impl LibPamHandle { 473 impl LibPamHandle {