Mercurial > crates > nonstick
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 { |
