Mercurial > crates > nonstick
diff src/handle.rs @ 153:3036f2e6a022 default tip
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). ///