diff src/handle.rs @ 73:ac6881304c78

Do conversations, along with way too much stuff. This implements conversations, along with all the memory management brouhaha that goes along with it. The conversation now lives directly on the handle rather than being a thing you have to get from it and then call manually. It Turns Out this makes things a lot easier! I guess we reorganized things again. For the last time. For real. I promise. This all passes ASAN, so it seems Pretty Good!
author Paul Fisher <paul@pfish.zone>
date Thu, 05 Jun 2025 03:41:38 -0400
parents 47eb242a4f88
children c30811b4afae
line wrap: on
line diff
--- a/src/handle.rs	Wed Jun 04 03:53:36 2025 -0400
+++ b/src/handle.rs	Thu Jun 05 03:41:38 2025 -0400
@@ -1,12 +1,6 @@
 //! The wrapper types and traits for handles into the PAM library.
-use crate::constants::{ErrorCode, Result};
+use crate::constants::Result;
 use crate::conv::Conversation;
-use crate::items::ItemType;
-use crate::module::ConversationMux;
-use crate::pam_ffi;
-use crate::pam_ffi::{memory, LibPamConversation, LibPamHandle};
-use std::ffi::{c_char, c_int};
-use std::{mem, ptr};
 
 macro_rules! trait_item {
     (get = $getter:ident, item = $item:literal $(, see = $see:path)? $(, $($doc:literal)*)?) => {
@@ -57,12 +51,23 @@
     };
 }
 
-/// Features of a PAM handle that are available to applications and modules.
+/// All-in-one trait for what you should expect from PAM as an application.
+pub trait PamHandleApplication: PamApplicationOnly + PamShared {}
+impl<T> PamHandleApplication for T where T: PamApplicationOnly + PamShared {}
+
+/// All-in-one trait for what you should expect from PAM as a module.
+pub trait PamHandleModule: PamModuleOnly + PamShared {}
+impl<T> PamHandleModule for T where T: PamModuleOnly + PamShared {}
+
+/// Functionality for both PAM applications and PAM modules.
 ///
-/// 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 {
-    type Conv: Conversation;
+/// This base trait includes features of a PAM handle that are available
+/// to both applications and modules.
+///
+/// You probably want [`LibPamHandle`](crate::pam_ffi::OwnedLibPamHandle).
+/// This trait is intended to allow creating mock PAM handle types
+/// to test PAM modules and applications.
+pub trait PamShared {
     /// Retrieves the name of the user who is authenticating or logging in.
     ///
     /// If the username has previously been obtained, this uses that username;
@@ -70,7 +75,7 @@
     ///
     ///  1. The prompt string passed to this function.
     ///  2. The string returned by `get_user_prompt_item`.
-    ///  3. The default prompt, `login: `
+    ///  3. The default prompt, `login: `.
     ///
     /// See the [`pam_get_user` manual page][man]
     /// or [`pam_get_user` in the Module Writer's Guide][mwg].
@@ -78,8 +83,8 @@
     /// # Example
     ///
     /// ```no_run
-    /// # use nonstick::PamModuleHandle;
-    /// # fn _doc(handle: &mut impl PamModuleHandle) -> Result<(), Box<dyn std::error::Error>> {
+    /// # use nonstick::PamShared;
+    /// # fn _doc(handle: &mut impl PamShared) -> 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.
@@ -92,16 +97,18 @@
     ///
     /// [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
-    fn get_user(&mut self, prompt: Option<&str>) -> Result<Option<&str>>;
+    fn get_user(&mut self, prompt: Option<&str>) -> Result<&str>;
 
     trait_item!(
         get = user_item,
         item = "PAM_USER",
+        see = Self::get_user,
         "The identity of the user for whom service is being requested."
         ""
-        "While PAM usually sets this automatically during the course of "
-        "a [`get_user`](Self::get_user) call, it may be changed by a module "
-        "over the course of the PAM transaction."
+        "Unlike [`get_user`](Self::get_user), this will simply get"
+        "the current state of the user item, and not request the username. "
+        "While PAM usually sets this automatically in the `get_user` call, "
+        "it may be changed by a module during the PAM transaction. "
         "Applications should check it after each step of the PAM process."
     );
     trait_item!(
@@ -225,9 +232,9 @@
 /// If you are not writing a PAM client application (e.g., you are writing
 /// a module), you should not use the functionality exposed by this trait.
 ///
-/// Like [`PamHandle`], this is intended to allow creating mock implementations
+/// Like [`PamShared`], this is intended to allow creating mock implementations
 /// of PAM for testing PAM applications.
-pub trait PamApplicationHandle: PamHandle {
+pub trait PamApplicationOnly {
     /// Closes the PAM session on an owned PAM handle.
     ///
     /// This should be called with the result of the application's last call
@@ -238,9 +245,9 @@
     /// See the [`pam_end` manual page][man] for more information.
     ///
     /// ```no_run
-    /// # use nonstick::PamApplicationHandle;
+    /// # use nonstick::handle::PamApplicationOnly;
     /// # use std::error::Error;
-    /// # fn _doc(handle: impl PamApplicationHandle, auth_result: nonstick::Result<()>) -> Result<(), Box<dyn Error>> {
+    /// # fn _doc(handle: impl PamApplicationOnly, auth_result: nonstick::Result<()>) -> Result<(), Box<dyn Error>> {
     /// // Earlier: authentication was performed and the result was stored
     /// // into auth_result.
     /// handle.close(auth_result)?;
@@ -250,9 +257,6 @@
     ///
     /// [man]: https://www.man7.org/linux/man-pages/man3/pam_end.3.html
     fn close(self, status: Result<()>) -> Result<()>;
-    
-    /// Uses a new PAM conversation.
-    fn set_conversation(&mut self, conversation: Self::Conv) -> Result<()>;
 }
 
 /// Functionality of a PAM handle that can be expected by a PAM module.
@@ -260,15 +264,9 @@
 /// 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
+/// Like [`PamShared`], this is intended to allow creating mock implementations
 /// of PAM for testing PAM modules.
-pub trait PamModuleHandle: PamHandle {
-    /// Gets a channel for communication with the user.
-    ///
-    /// The Conversation is the conduit which you use for all communication
-    /// with the user.
-    fn conversation(&mut self) -> Result<ConversationMux<'_, Self::Conv>>;
-
+pub trait PamModuleOnly: Conversation {
     /// Retrieves the authentication token from the user.
     ///
     /// This should only be used by *authentication* and *password-change*
@@ -280,8 +278,8 @@
     /// # Example
     ///
     /// ```no_run
-    /// # use nonstick::PamModuleHandle;
-    /// # fn _doc(handle: &mut impl PamModuleHandle) -> Result<(), Box<dyn std::error::Error>> {
+    /// # use nonstick::handle::PamModuleOnly;
+    /// # fn _doc(handle: &mut impl PamModuleOnly) -> 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.
@@ -292,7 +290,7 @@
     ///
     /// [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
-    fn get_authtok(&mut self, prompt: Option<&str>) -> Result<Option<&str>>;
+    fn get_authtok(&mut self, prompt: Option<&str>) -> Result<&str>;
 
     trait_item!(
         get = authtok_item,
@@ -351,135 +349,3 @@
         fn set_data<T>(&mut self, key: &str, data: Box<T>) -> Result<()>;
      */
 }
-
-
-impl LibPamHandle {
-    /// Gets a C string item.
-    ///
-    /// # Safety
-    ///
-    /// You better be requesting an item which is a C string.
-    unsafe fn get_cstr_item(&mut self, item_type: ItemType) -> Result<Option<&str>> {
-        let mut output = ptr::null();
-        let ret = unsafe { pam_ffi::pam_get_item(self, item_type as c_int, &mut output) };
-        ErrorCode::result_from(ret)?;
-        memory::wrap_string(output.cast())
-    }
-
-    /// Sets a C string item.
-    ///
-    /// # Safety
-    ///
-    /// You better be setting an item which is a C string.
-    unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&str>) -> Result<()> {
-        let data_str = memory::option_cstr(data)?;
-        let ret = unsafe {
-            pam_ffi::pam_set_item(
-                self,
-                item_type as c_int,
-                memory::prompt_ptr(data_str.as_ref()).cast(),
-            )
-        };
-        ErrorCode::result_from(ret)
-    }
-}
-
-impl Drop for LibPamHandle {
-    /// Ends the PAM session with a zero error code.
-    /// You probably want to call [`close`](Self::close) instead of
-    /// letting this drop by itself.
-    fn drop(&mut self) {
-        unsafe {
-            pam_ffi::pam_end(self, 0);
-        }
-    }
-}
-
-macro_rules! cstr_item {
-    (get = $getter:ident, item = $item_type:path) => {
-        fn $getter(&mut self) -> Result<Option<&str>> {
-            unsafe { self.get_cstr_item($item_type) }
-        }
-    };
-    (set = $setter:ident, item = $item_type:path) => {
-        fn $setter(&mut self, value: Option<&str>) -> Result<()> {
-            unsafe { self.set_cstr_item($item_type, value) }
-        }
-    };
-}
-
-impl PamHandle for LibPamHandle {
-    type Conv = LibPamConversation;
-    fn get_user(&mut self, prompt: Option<&str>) -> Result<Option<&str>> {
-        let prompt = memory::option_cstr(prompt)?;
-        let mut output: *const c_char = ptr::null();
-        let ret = unsafe {
-            pam_ffi::pam_get_user(self, &mut output, memory::prompt_ptr(prompt.as_ref()))
-        };
-        ErrorCode::result_from(ret)?;
-        unsafe { memory::wrap_string(output) }
-    }
-
-    cstr_item!(get = user_item, item = ItemType::User);
-    cstr_item!(set = set_user_item, item = ItemType::User);
-    cstr_item!(get = service, item = ItemType::Service);
-    cstr_item!(set = set_service, item = ItemType::Service);
-    cstr_item!(get = user_prompt, item = ItemType::UserPrompt);
-    cstr_item!(set = set_user_prompt, item = ItemType::UserPrompt);
-    cstr_item!(get = tty_name, item = ItemType::Tty);
-    cstr_item!(set = set_tty_name, item = ItemType::Tty);
-    cstr_item!(get = remote_user, item = ItemType::RemoteUser);
-    cstr_item!(set = set_remote_user, item = ItemType::RemoteUser);
-    cstr_item!(get = remote_host, item = ItemType::RemoteHost);
-    cstr_item!(set = set_remote_host, item = ItemType::RemoteHost);
-    cstr_item!(set = set_authtok_item, item = ItemType::AuthTok);
-    cstr_item!(set = set_old_authtok_item, item = ItemType::OldAuthTok);
-}
-
-impl PamApplicationHandle for LibPamHandle {
-    fn close(mut self, status: Result<()>) -> Result<()> {
-        let result = unsafe { pam_ffi::pam_end(&mut self, 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)
-    }
-
-    fn set_conversation(&mut self, conversation: Self::Conv) -> Result<()> {
-        todo!()
-    }
-}
-
-impl PamModuleHandle for LibPamHandle {
-    fn conversation(&mut self) -> Result<ConversationMux<'_, Self::Conv>> {
-        todo!()
-    }
-
-    fn get_authtok(&mut self, prompt: Option<&str>) -> Result<Option<&str>> {
-        let prompt = memory::option_cstr(prompt)?;
-        let mut output: *const c_char = ptr::null_mut();
-        let res = unsafe {
-            pam_ffi::pam_get_authtok(
-                self,
-                ItemType::AuthTok.into(),
-                &mut output,
-                memory::prompt_ptr(prompt.as_ref()),
-            )
-        };
-        ErrorCode::result_from(res)?;
-        unsafe { memory::wrap_string(output) }
-    }
-
-    cstr_item!(get = authtok_item, item = ItemType::AuthTok);
-    cstr_item!(get = old_authtok_item, item = ItemType::OldAuthTok);
-}
-
-/// 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());
-    }
-}