view src/handle.rs @ 171:e27c5c667a5a

Create full new types for return code and flags, separate end to end. This plumbs the ReturnCode and RawFlags types through the places where we call into or are called from PAM. Also adds Sun documentation to the project.
author Paul Fisher <paul@pfish.zone>
date Fri, 25 Jul 2025 20:52:14 -0400
parents 77470e45e397
children 9e4ce1631bd3
line wrap: on
line source

//! The wrapper types and traits for handles into the PAM library.

use crate::_doc::{guide, linklist, man7, manbsd, stdlinks};
use crate::constants::{AuthnFlags, AuthtokFlags, Result};
use crate::conv::Conversation;
use crate::environ::{EnvironMap, EnvironMapMut};
use crate::items::{getter, Items, ItemsMut};
use crate::logging::Logger;
use std::ffi::{OsStr, OsString};

/// Functionality for both PAM applications and PAM modules.
///
/// This base trait includes features of a PAM handle that are available
/// to both applications and modules.
///
/// You probably want [`LibPamTransaction`](crate::libpam::LibPamTransaction).
/// This trait is intended to allow creating mock PAM handle types
/// to test PAM modules and applications.
pub trait PamShared: Logger {
    /// Retrieves the name of the user who is authenticating or logging in.
    ///
    /// If the username has previously been obtained, this uses that username;
    /// otherwise it prompts the user with the first of these that is present:
    ///
    ///  1. The prompt string passed to this function.
    ///  2. The string returned by `get_user_prompt_item`.
    ///  3. The default prompt, `login: `.
    ///
    /// # References
    #[doc = linklist!(pam_get_user: mwg, _std)]
    ///
    /// # Example
    ///
    /// ```no_run
    /// # 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.username(None)?;
    /// // Get the username using a custom prompt.
    /// // If this were actually called right after the above,
    /// // both user and user_2 would have the same value.
    /// let user_2 = handle.username(Some("who ARE you even???".as_ref()))?;
    /// # Ok(())
    /// # }
    /// ```
    #[doc = stdlinks!(3 pam_get_user)]
    #[doc = guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_get_user")]
    fn username(&mut self, prompt: Option<&OsStr>) -> Result<OsString>;

    /// The contents of the environment to set for the logged-in user.
    ///
    /// # References
    ///
    #[doc = linklist!(pam_getenv: adg, mwg, _std)]
    ///
    #[doc = stdlinks!(3 pam_getenv)]
    #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_getenv")]
    #[doc = guide!(mwg: "mwg-expected-by-module-item.html#adg-pam_getenv")]
    fn environ(&self) -> impl EnvironMap;

    /// A writable map of the environment to set for the logged-in user.
    ///
    /// # References
    ///
    #[doc = linklist!(pam_putenv: adg, mwg, _std)]
    ///
    #[doc = stdlinks!(3 pam_putenv)]
    #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_putenv")]
    #[doc = guide!(mwg: "mwg-expected-by-module-item.html#adg-pam_putenv")]
    fn environ_mut(&mut self) -> impl EnvironMapMut;

    /// Gets Items, data shared by PAM, the application, and modules.
    ///
    /// Certain Items should not be accessed by a PAM application;
    /// those are available directly on [`ModuleClient`] for use
    /// by PAM modules only.
    ///
    /// # References
    ///
    #[doc = linklist!(pam_get_item: mwg, adg, _std)]
    ///
    #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_get_item")]
    #[doc = guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_get_item")]
    #[doc = stdlinks!(3 pam_get_item)]
    fn items(&self) -> impl Items;

    /// Read-write access to PAM Items.
    ///
    /// # References
    ///
    #[doc = linklist!(pam_set_item: mwg, adg, _std)]
    ///
    #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_set_item")]
    #[doc = guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_set_item")]
    #[doc = stdlinks!(3 pam_set_item)]
    fn items_mut(&mut self) -> impl ItemsMut;
}

/// Functionality of a PAM handle that can be expected by a PAM application.
///
/// 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 [`PamShared`], this is intended to allow creating mock implementations
/// of PAM for testing PAM applications.
pub trait Transaction: PamShared {
    /// Starts the authentication process for the user.
    ///
    /// The application calls this to find out who the user is, and verify that
    /// they are really that person. If authentication is successful,
    /// this will return an `Ok(())` [`Result`].
    ///
    /// A PAM module may change the caller's [username](PamShared::username)
    /// as part of the login process, so be sure to check it after making
    /// any PAM application call.
    ///
    /// # References
    #[doc = linklist!(pam_authenticate: adg, _std)]
    ///
    #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_authenticate")]
    #[doc = stdlinks!(3 pam_authenticate)]
    fn authenticate(&mut self, flags: AuthnFlags) -> Result<()>;

    /// Verifies the validity of the user's account (and other stuff).
    ///
    /// After [authentication](Self::authenticate), an application should call
    /// this to ensure that the user's account is still valid. This may check
    /// for token expiration or that the user's account is not locked.
    ///
    /// # References
    #[doc = linklist!(pam_acct_mgmt: adg, _std)]
    ///
    #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_acct_mgmt")]
    #[doc = stdlinks!(3 pam_acct_mgmt)]
    fn account_management(&mut self, flags: AuthnFlags) -> Result<()>;

    /// Changes the authentication token.
    ///
    /// # References
    #[doc = linklist!(pam_chauthtok: adg, _std)]
    ///
    #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_chauthtok")]
    #[doc = stdlinks!(3 pam_chauthtok)]
    fn change_authtok(&mut self, flags: AuthtokFlags) -> 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 [`PamShared`], this is intended to allow creating mock implementations
/// of PAM for testing PAM modules.
pub trait ModuleClient: Conversation + PamShared {
    /// Retrieves the authentication token from the user.
    ///
    /// This should only be used by *authentication* and *password-change*
    /// PAM modules.
    ///
    /// With Sun's PAM implementation, this works a little bit differently
    /// than it does everywhere else. Sun's PAM provides for password input
    /// *exclusively* though module stacking with the
    /// [`pam_authtok_get` module][pam_authtok_get]. On Sun, this function
    /// is exactly equivalent to [`Self::authtok_item`], in that it only
    /// retrieves the existing item.
    ///
    /// # References
    ///
    #[doc = linklist!(pam_get_authtok: man7, manbsd)]
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nonstick::handle::ModuleClient;
    /// # fn _doc(handle: &mut impl ModuleClient) -> Result<(), Box<dyn std::error::Error>> {
    /// // Get the user's password using the default prompt.
    /// let pass = handle.authtok(None)?;
    /// // Get the user's password using a custom prompt.
    /// let pass = handle.authtok(Some("Reveal your secrets!".as_ref()))?;
    /// Ok(())
    /// # }
    /// ```
    #[doc = man7!(3 pam_get_authtok)]
    #[doc = manbsd!(3 pam_get_authtok)]
    /// [pam_authtok_get]: https://smartos.org/man/7/pam_authtok_get
    fn authtok(&mut self, prompt: Option<&OsStr>) -> Result<OsString>;

    /// Retrieves the user's old authentication token when changing passwords.
    ///
    /// This should only be used by a *password-change* module.
    ///
    /// # References
    ///
    #[doc = linklist!(pam_get_authtok: man7, manbsd)]
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nonstick::handle::ModuleClient;
    /// # fn _doc(handle: &mut impl ModuleClient) -> Result<(), Box<dyn std::error::Error>> {
    /// // Get the user's password using the default prompt.
    /// let pass = handle.old_authtok(None)?;
    /// // Get the user's password using a custom prompt.
    /// let pass = handle.old_authtok(Some("Reveal your secrets!".as_ref()))?;
    /// Ok(())
    /// # }
    /// ```
    ///
    #[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).
        ///
        /// This is normally set automatically by PAM through [`Self::authtok`],
        /// but this will get its value (if set) without prompting the user.
        ///
        /// Like `authtok`, this should only ever be called
        /// by *authentication* and *password-change* PAM modules.
        ///
        /// # References
        ///
        #[doc = linklist!(pam_set_item: mwg, adg, _std)]
        ///
        #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_set_item")]
        #[doc = guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_set_item")]
        #[doc = stdlinks!(3 pam_set_item)]
        authtok_item("PAM_AUTHTOK", see = Self::authtok)
    );

    getter!(
        /// Gets the user's old authentication token when changing passwords.
        ///
        /// This is normally set automatically by PAM through
        /// [`Self::old_authtok`], but this will get its value (if set)
        /// without prompting the user.
        ///
        /// This should only ever be called by *password-change* PAM modules.
        ///
        /// # References
        ///
        #[doc = linklist!(pam_set_item: mwg, adg, _std)]
        ///
        #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_set_item")]
        #[doc = guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_set_item")]
        #[doc = stdlinks!(3 pam_set_item)]
        old_authtok_item("PAM_OLDAUTHTOK", see = ItemsMut::set_old_authtok)
    );
}