Mercurial > crates > nonstick
diff src/handle.rs @ 66:a674799a5cd3
Make `PamHandle` and `PamModuleHandle` traits.
This creates traits for PAM functionality and pulls the definitions
of that functionality out of the original `PamHandle` (renamed to
`LibPamHandle`) and into those traits. This supports testing PAM
module implementations using mock PAM library implementations.
Also uses a better representation of opaque pointers.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Tue, 27 May 2025 14:37:28 -0400 |
parents | bbe84835d6db |
children |
line wrap: on
line diff
--- a/src/handle.rs Thu May 22 02:08:10 2025 -0400 +++ b/src/handle.rs Tue May 27 14:37:28 2025 -0400 @@ -1,18 +1,17 @@ -//! Where [PamHandle] lives. +//! The wrapper types and traits for handles into the PAM library. +use crate::constants::{ErrorCode, Result}; use crate::items::{Item, ItemType}; -use crate::{memory, pam_ffi, ErrorCode}; +use crate::{memory, pam_ffi}; use libc::c_char; use secure_string::SecureString; use std::ffi::{c_int, CString}; +use std::mem; -/// Your interface to a PAM handle. +/// Features of a PAM handle that are available to applications and modules. /// -/// This structure wraps an opaque PAM-provided pointer and gives you -/// a safe and familiar struct-based API to interact with PAM. -#[repr(transparent)] -pub struct PamHandle(*mut libc::c_void); - -impl PamHandle { +/// You probably want [`LibPamHandle`]. This trait is intended to allow creating +/// mock PAM handle types used for testing PAM modules and applications. +pub trait PamHandle { /// Retrieves the name of the user who is authenticating or logging in. /// /// This is effectively like `handle.get_item::<Item::User>()`. @@ -23,7 +22,7 @@ /// /// ```no_run /// # use nonstick::PamHandle; - /// # fn _doc(handle: &PamHandle) -> Result<(), Box<dyn std::error::Error>> { + /// # fn _doc(handle: &impl PamHandle) -> Result<(), Box<dyn std::error::Error>> { /// // Get the username using the default prompt. /// let user = handle.get_user(None)?; /// // Get the username using a custom prompt. @@ -31,18 +30,10 @@ /// # Ok(()) /// # } /// ``` - /// + /// /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_user - pub fn get_user(&self, prompt: Option<&str>) -> crate::Result<String> { - let prompt = memory::option_cstr(prompt)?; - let mut output: *const c_char = std::ptr::null_mut(); - let ret = unsafe { - pam_ffi::pam_get_user(self.0, &mut output, memory::prompt_ptr(prompt.as_ref())) - }; - ErrorCode::result_from(ret)?; - memory::copy_pam_string(output) - } + fn get_user(&self, prompt: Option<&str>) -> Result<String>; /// Retrieves the authentication token from the user. /// @@ -55,7 +46,7 @@ /// /// ```no_run /// # use nonstick::PamHandle; - /// # fn _doc(handle: &PamHandle) -> Result<(), Box<dyn std::error::Error>> { + /// # fn _doc(handle: &impl PamHandle) -> Result<(), Box<dyn std::error::Error>> { /// // Get the user's password using the default prompt. /// let pass = handle.get_authtok(None)?; /// // Get the user's password using a custom prompt. @@ -63,23 +54,10 @@ /// Ok(()) /// # } /// ``` - /// + /// /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item - pub fn get_authtok(&self, prompt: Option<&str>) -> crate::Result<SecureString> { - let prompt = memory::option_cstr(prompt)?; - let mut output: *const c_char = std::ptr::null_mut(); - let res = unsafe { - pam_ffi::pam_get_authtok( - self.0, - ItemType::AuthTok.into(), - &mut output, - memory::prompt_ptr(prompt.as_ref()), - ) - }; - ErrorCode::result_from(res)?; - memory::copy_pam_string(output).map(SecureString::from) - } + fn get_authtok(&self, prompt: Option<&str>) -> Result<SecureString>; /// Retrieves an [Item] that has been set, possibly by the PAM client. /// @@ -96,7 +74,7 @@ /// # use nonstick::PamHandle; /// use nonstick::items::Service; /// - /// # fn _doc(pam_handle: &PamHandle) -> Result<(), Box<dyn std::error::Error>> { + /// # fn _doc(pam_handle: &impl PamHandle) -> Result<(), Box<dyn std::error::Error>> { /// let svc: Option<Service> = pam_handle.get_item()?; /// match svc { /// Some(name) => eprintln!("The calling service name is {:?}", name.to_string_lossy()), @@ -108,19 +86,7 @@ /// /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item - pub fn get_item<T: Item>(&self) -> crate::Result<Option<T>> { - let mut ptr: *const libc::c_void = std::ptr::null(); - let out = unsafe { - let ret = pam_ffi::pam_get_item(self.0, T::type_id().into(), &mut ptr); - ErrorCode::result_from(ret)?; - let typed_ptr: *const T::Raw = ptr.cast(); - match typed_ptr.is_null() { - true => None, - false => Some(T::from_raw(typed_ptr)), - } - }; - Ok(out) - } + fn get_item<T: Item>(&self) -> Result<Option<T>>; /// Sets an item in the PAM context. It can be retrieved using [`get_item`](Self::get_item). /// @@ -129,12 +95,40 @@ /// /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_item - pub fn set_item<T: Item>(&mut self, item: T) -> crate::Result<()> { - let ret = - unsafe { pam_ffi::pam_set_item(self.0, T::type_id().into(), item.into_raw().cast()) }; - ErrorCode::result_from(ret) - } + fn set_item<T: Item>(&mut self, item: T) -> Result<()>; + /// Closes the PAM session on an owned PAM handle. + /// + /// This should be called with the result of the application's last call + /// into PAM services. Since this is only applicable to *owned* PAM handles, + /// a PAM module should never call this (and it will never be handed + /// an owned `PamHandle` that it can `close`). + /// + /// See the [`pam_end` manual page][man] for more information. + /// + /// ```no_run + /// # use nonstick::PamHandle; + /// # use std::error::Error; + /// # fn _doc(handle: impl PamHandle, auth_result: nonstick::Result<()>) -> Result<(), Box<dyn Error>> { + /// // Earlier: authentication was performed and the result was stored + /// // into auth_result. + /// handle.close(auth_result)?; + /// # Ok(()) + /// # } + /// ``` + /// + /// [man]: https://www.man7.org/linux/man-pages/man3/pam_end.3.html + fn close(self, status: Result<()>) -> Result<()>; +} + +/// Functionality of a PAM handle that can be expected by a PAM module. +/// +/// If you are not writing a PAM module (e.g., you are writing an application), +/// you should not use any of the functionality exposed by this trait. +/// +/// Like [`PamHandle`], this is intended to allow creating mock implementations +/// of PAM for testing PAM modules. +pub trait PamModuleHandle: PamHandle { /// Gets some pointer, identified by `key`, that has been set previously /// using [`set_data`](Self::set_data). /// @@ -151,18 +145,7 @@ /// /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_data - pub unsafe fn get_data<T>(&self, key: &str) -> crate::Result<Option<&T>> { - let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?; - let mut ptr: *const libc::c_void = std::ptr::null(); - ErrorCode::result_from(pam_ffi::pam_get_data(self.0, c_key.as_ptr(), &mut ptr))?; - match ptr.is_null() { - true => Ok(None), - false => { - let typed_ptr = ptr.cast(); - Ok(Some(&*typed_ptr)) - } - } - } + unsafe fn get_data<T>(&self, key: &str) -> Result<Option<&T>>; /// Stores a pointer that can be retrieved later with [`get_data`](Self::get_data). /// @@ -176,33 +159,126 @@ /// /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_data - pub fn set_data<T>(&mut self, key: &str, data: Box<T>) -> crate::Result<()> { - let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?; + fn set_data<T>(&mut self, key: &str, data: Box<T>) -> Result<()>; +} + +/// A [`PamHandle`] backed by `libpam`, i.e., a real PAM handle. +/// +/// This structure wraps an opaque PAM handle and gives you a nice Rusty +/// interface to use PAM. +#[repr(C)] +pub struct LibPamHandle(pam_ffi::Handle); + +impl LibPamHandle { + /// Converts a pointer passed from PAM into a borrowed handle. + /// + /// # Safety + /// + /// It is your responsibility to provide a valid pointer. + pub unsafe fn from_ptr<'a>(ptr: *mut libc::c_void) -> &'a mut LibPamHandle { + &mut *(ptr as *mut LibPamHandle) + } +} + +impl Drop for LibPamHandle { + /// Ends the PAM session with a zero error code. + fn drop(&mut self) { + unsafe { + pam_ffi::pam_end(&mut self.0, 0); + } + } +} + +impl PamHandle for LibPamHandle { + fn get_user(&self, prompt: Option<&str>) -> crate::Result<String> { + let prompt = memory::option_cstr(prompt)?; + let mut output: *const c_char = std::ptr::null_mut(); let ret = unsafe { - pam_ffi::pam_set_data( - self.0, - c_key.as_ptr(), - Box::into_raw(data).cast(), - Self::set_data_cleanup::<T>, + pam_ffi::pam_get_user(&self.0, &mut output, memory::prompt_ptr(prompt.as_ref())) + }; + ErrorCode::result_from(ret)?; + memory::copy_pam_string(output) + } + + fn get_authtok(&self, prompt: Option<&str>) -> crate::Result<SecureString> { + let prompt = memory::option_cstr(prompt)?; + let mut output: *const c_char = std::ptr::null_mut(); + let res = unsafe { + pam_ffi::pam_get_authtok( + &self.0, + ItemType::AuthTok.into(), + &mut output, + memory::prompt_ptr(prompt.as_ref()), ) }; + ErrorCode::result_from(res)?; + memory::copy_pam_string(output).map(SecureString::from) + } + + fn get_item<T: Item>(&self) -> crate::Result<Option<T>> { + let mut ptr: *const libc::c_void = std::ptr::null(); + let out = unsafe { + let ret = pam_ffi::pam_get_item(&self.0, T::type_id().into(), &mut ptr); + ErrorCode::result_from(ret)?; + let typed_ptr: *const T::Raw = ptr.cast(); + match typed_ptr.is_null() { + true => None, + false => Some(T::from_raw(typed_ptr)), + } + }; + Ok(out) + } + + fn set_item<T: Item>(&mut self, item: T) -> crate::Result<()> { + let ret = unsafe { + pam_ffi::pam_set_item(&mut self.0, T::type_id().into(), item.into_raw().cast()) + }; ErrorCode::result_from(ret) } - /// 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()); - } + fn close(mut self, status: Result<()>) -> Result<()> { + let result = unsafe { pam_ffi::pam_end(&mut self.0, ErrorCode::result_to_c(status)) }; + // Since we've already `pam_end`ed this session, we don't want it to be + // double-freed on drop. + mem::forget(self); + ErrorCode::result_from(result) } } -impl From<*mut libc::c_void> for PamHandle { - /// Wraps an internal Handle pointer. - fn from(value: *mut libc::c_void) -> Self { - Self(value) +impl PamModuleHandle for LibPamHandle { + unsafe fn get_data<T>(&self, key: &str) -> crate::Result<Option<&T>> { + let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?; + let mut ptr: *const libc::c_void = std::ptr::null(); + ErrorCode::result_from(pam_ffi::pam_get_data(&self.0, c_key.as_ptr(), &mut ptr))?; + match ptr.is_null() { + true => Ok(None), + false => { + let typed_ptr = ptr.cast(); + Ok(Some(&*typed_ptr)) + } + } + } + + fn set_data<T>(&mut self, key: &str, data: Box<T>) -> crate::Result<()> { + let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?; + let ret = unsafe { + pam_ffi::pam_set_data( + &mut self.0, + c_key.as_ptr(), + Box::into_raw(data).cast(), + set_data_cleanup::<T>, + ) + }; + ErrorCode::result_from(ret) } } + +/// 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()); + } +}