diff src/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 1bc52025156b
children
line wrap: on
line diff
--- a/src/handle.rs	Mon Jul 07 19:05:31 2025 -0400
+++ b/src/handle.rs	Tue Jul 08 00:31:54 2025 -0400
@@ -235,6 +235,153 @@
     #[doc = stdlinks!(3 pam_get_authtok)]
     fn old_authtok(&mut self, prompt: Option<&OsStr>) -> Result<OsString>;
 
+    /// Gets an item of module-specific data stored over the transaction.
+    ///
+    /// This gives you a reference to the data that was earlier set with
+    /// [`Self::set_module_data`]. If not present, you get `None`.
+    ///
+    /// Data is in a module-specific, type-specific namespace.
+    ///
+    /// ```
+    /// # use nonstick::ModuleClient;
+    /// # use std::path::PathBuf;
+    /// # fn test(client: &impl ModuleClient) {
+    /// // These two can coexist and do not overlap.
+    /// let str_data: Option<&String> = client.get_module_data("the_key");
+    /// let num_data: Option<&u64> = client.get_module_data("the_key");
+    /// // ...
+    /// let nothing_data: Option<&PathBuf> = client.get_module_data("this does not exist");
+    /// # }
+    /// ```
+    ///
+    /// # References
+    ///
+    #[doc = linklist!(pam_get_data: mwg, _std)]
+    ///
+    #[doc = guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_get_data")]
+    #[doc = stdlinks!(3 pam_get_data)]
+
+    fn get_module_data<T: 'static>(&self, key: &str) -> Option<&T>;
+
+    /// Sets module-specific data.
+    ///
+    /// A PAM module may need to store data across multiple invocations within
+    /// the same PAM transaction. For instance, a module that stores credentials
+    /// would need to know where those credentials were stored in order to
+    /// update or destroy them later. Also see [`Self::get_module_data`].
+    ///
+    /// Module-specific data gives a module a way to store such data.
+    /// Data are stored in a module-specific, type-specific namespace.
+    ///
+    /// PAM takes ownership of the data passed in. See the **Cleanup** section
+    /// below for details on how cleanup is handled.
+    ///
+    /// # Examples
+    ///
+    /// Each type of data is in a separate namespace:
+    ///
+    /// ```
+    /// # use nonstick::{ModuleClient, Result};
+    /// # fn test(client: &mut impl ModuleClient) -> Result<()> {
+    /// client.set_module_data("count", 999i32)?;
+    ///
+    /// let count_int: Option<&i32> = client.get_module_data("count");
+    /// // count_int = Some(&999i32)
+    /// let count_string: Option<&String> = client.get_module_data("count");
+    /// // count_string = None
+    /// # Ok(())
+    /// # }
+    /// ```
+    ///
+    /// Data persist across invocations of the same module:
+    ///
+    /// ```
+    /// # use nonstick::{ModuleClient, Result};
+    /// // In a pam_authenticate call, this function is called:
+    /// fn authenticate(client: &mut impl ModuleClient) -> Result<()> {
+    ///     client.set_module_data::<u64>("TOKEN_ID", 0x0fa1afe10000beef)?;
+    ///     Ok(())
+    /// }
+    ///
+    /// // Later, in a pam_session_start call:
+    /// fn start_session(client: &mut impl ModuleClient) -> Result<()> {
+    ///     match client.get_module_data::<u64>("TOKEN_ID") {
+    ///         Some(&tid) => {
+    ///             // This will execute and tid will be 0x0fa1afe10000beef.
+    ///         },
+    ///         None => { /* This will not execute. */ },
+    ///     }
+    ///     Ok(())
+    /// }
+    /// ```
+    ///
+    /// Each module has its own set of data:
+    ///
+    /// ```
+    /// # use nonstick::{ModuleClient, Result};
+    /// // This function is called somewhere in pam_module_a.so.
+    /// fn in_pam_module_a(client: &mut impl ModuleClient) -> Result<()> {
+    ///     client.set_module_data("value", String::from("pam_module_a data"))?;
+    ///     Ok(())
+    /// }
+    ///
+    /// // This function is called later in pam_module_b.so.
+    /// fn in_pam_module_b(client: &mut impl ModuleClient) -> Result<()> {
+    ///     match client.get_module_data::<String>("value") {
+    ///         Some(value) => {
+    ///             // This will match, because pam_module_a's data
+    ///             // is completely unrelated to pam_module_b's data.
+    ///         },
+    ///         None => {
+    ///             // This branch will execute.
+    ///         },
+    ///     }
+    ///     // ...
+    /// #    Ok(())
+    /// }
+    /// ```
+    ///
+    /// # Cleanup
+    ///
+    /// PAM modules should be careful about cleaning up data outside their own
+    /// address space, because PAM applications may `fork()`:
+    ///
+    /// ```plain
+    /// ┃ let tx = start_pam_transaction();
+    /// ┃
+    /// ┃ tx.authenticate();
+    /// ┃ │ // PAM calls into your module where you set data:
+    /// ┃ │ handle.set_module_data("key", the_data);
+    /// ┃
+    /// ┃ fork();
+    /// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+    /// Parent process             Child process
+    /// ┃ wait(child);             ┃ setuid(user_to_login);
+    /// ┃ ┆                        ┃ // ... do other stuff ...
+    /// ┃ ┆                        ┃ drop(tx);
+    /// ┃ ┆                        ┃ │ // PAM cleans up your data.
+    /// ┃ ┆                        ┃ │ drop(the_data);
+    /// ┃ ┆                        ┗ exec(user's shell)
+    /// ┃ ┆                          ┃ // user does stuff over their session
+    /// ┃ ┆                          ┃ // ...
+    /// ┃ ┆                          X
+    /// ┃
+    /// ┃ drop(tx);
+    /// ┃ │ // Parent PAM cleans up your data.
+    /// ┃ │ drop(the_data);  // Called again, but in this process instead!
+    /// ```
+    ///
+    /// While LibPAM offers a way to customize the action taken on cleanup,
+    /// we do not (yet) offer this.
+    ///
+    /// # References
+    ///
+    #[doc = linklist!(pam_set_data: mwg, _std)]
+    ///
+    #[doc = guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_set_data")]
+    #[doc = stdlinks!(3 pam_set_data)]
+    fn set_module_data<T: 'static>(&mut self, key: &str, data: T) -> Result<()>;
+
     getter!(
         /// Gets the user's authentication token (e.g., password).
         ///