view src/handle.rs @ 141:a508a69c068a

Remove a lot of Results from functions. Many functions are documented to only return failing Results when given improper inputs or when there is a memory allocation failure (which can be verified by looking at the source). In cases where we know our input is correct, we don't need to check for memory allocation errors for the same reason that Rust doesn't do so when you, e.g., create a new Vec.
author Paul Fisher <paul@pfish.zone>
date Sat, 05 Jul 2025 17:16:56 -0400
parents a12706e42c9d
children ebb71a412b58
line wrap: on
line source

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

use crate::constants::{Flags, Result};
use crate::conv::Conversation;
use crate::environ::{EnvironMap, EnvironMapMut};
use crate::logging::{Level, Location};
use crate::{guide, linklist, man7, manbsd, stdlinks};

macro_rules! trait_item {
    ($(#[$md:meta])* get = $getter:ident, item = $item:literal $(, see = $see:path)?) => {
        $(#[$md])*
        #[doc = ""]
        #[doc = concat!("Gets the `", $item, "` of the PAM handle.")]
        $(
            #[doc = concat!("See [`", stringify!($see), "`].")]
        )?
        ///
        /// Returns a reference to the item's value, owned by PAM.
        /// The item is assumed to be valid UTF-8 text.
        /// If it is not, `ConversationError` is returned.
        ///
        /// # 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 $getter(&self) -> Result<Option<String>>;
    };
    ($(#[$md:meta])* set = $setter:ident, item = $item:literal $(, see = $see:path)?) => {
        $(#[$md])*
        #[doc = ""]
        #[doc = concat!("Sets the `", $item, "` from the PAM handle.")]
        $(
            #[doc = concat!("See [`", stringify!($see), "`].")]
        )?
        ///
        /// Sets the item's value. PAM copies the string's contents.
        /// If the string contains a null byte, this will return
        /// a `ConversationError`.
        ///
        /// # 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 $setter(&mut self, value: Option<&str>) -> Result<()>;
    };
}

/// 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 [`LibPamHandle`](crate::libpam::OwnedLibPamHandle).
/// This trait is intended to allow creating mock PAM handle types
/// to test PAM modules and applications.
pub trait PamShared {
    /// Logs something via this PAM handle.
    ///
    /// You probably want to use one of the logging macros,
    /// like [`error!`](crate::error!),
    /// [`warn!`](crate::warn!),
    /// [`info!`](crate::info!),
    /// or [`debug!`](crate::debug!).
    ///
    /// In most PAM implementations, this will go to syslog.
    /// See [Linux-PAM's `pam_syslog`][man7] or
    /// [OpenPAM's `openpam_log`][manbsd] for more details.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nonstick::PamShared;
    /// use nonstick::logging::Level;
    /// use nonstick::location;
    /// # fn _test(pam_hdl: impl PamShared) {
    /// # let delay_ms = 100;
    /// # let url = "https://zombo.com";
    /// // Usually, instead of calling this manually, just use the macros.
    /// nonstick::error!(pam_hdl, "something bad happened!");
    /// nonstick::warn!(pam_hdl, "loading information took {delay_ms} ms");
    /// nonstick::info!(pam_hdl, "using network backend");
    /// nonstick::debug!(pam_hdl, "sending GET request to {url}");
    /// // But if you really want to, you can call this yourself:
    /// pam_hdl.log(Level::Warning, location!(), "this is unnecessarily verbose");
    /// # }
    /// ```
    #[doc = man7!(3 pam_syslog)]
    #[doc = manbsd!(3 openpam_log)]
    fn log(&self, level: Level, loc: Location<'_>, entry: &str);

    /// 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???"))?;
    /// # 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<&str>) -> Result<String>;

    /// The contents of the environment to set, read-only.
    fn environ(&self) -> impl EnvironMap;

    /// A writable version of the environment.
    fn environ_mut(&mut self) -> impl EnvironMapMut;

    trait_item!(
        /// The identity of the user for whom service is being requested.
        ///
        /// Unlike [`username`](Self::username), this will simply get
        /// the current state of the user item, and not request the username.
        /// While PAM usually sets this automatically in the `username` call,
        /// it may be changed by a module during the PAM transaction.
        /// Applications should check it after each step of the PAM process.
        get = user_item,
        item = "PAM_USER",
        see = Self::username
    );
    trait_item!(
        /// Sets the identity of the logging-in user.
        ///
        /// Usually this will be set during the course of
        /// a [`username`](Self::username) call, but you may set it manually
        /// or change it during the PAM process.
        set = set_user_item,
        item = "PAM_USER",
        see = Self::user_item
    );

    trait_item!(
        /// The service name, which identifies the PAM stack which is used
        /// to perform authentication.
        get = service,
        item = "PAM_SERVICE"
    );
    trait_item!(
        /// Sets the service name. It's probably a bad idea to change this.
        set = set_service,
        item = "PAM_SERVICE",
        see = Self::service
    );

    trait_item!(
        /// The string used to prompt for a user's name.
        /// By default, this is a localized version of `login: `.
        get = user_prompt,
        item = "PAM_USER_PROMPT"
    );
    trait_item!(
        /// Sets the string used to prompt for a user's name.
        set = set_user_prompt,
        item = "PAM_USER_PROMPT",
        see = Self::user_prompt
    );

    trait_item!(
        /// The device path of the TTY being used to log in.
        ///
        /// This is the terminal the user is logging in on,
        /// specified as the full device path (e.g. `/dev/tty0`).
        /// Very old applications may use this instead of `PAM_XDISPLAY`.
        get = tty_name,
        item = "PAM_TTY"
    );
    trait_item!(
        /// Sets the path to the terminal where the user is logging on.
        set = set_tty_name,
        item = "PAM_TTY",
        see = Self::tty_name
    );

    trait_item!(
        /// If set, the identity of the remote user logging in.
        ///
        /// This is only as trustworthy as the application calling PAM.
        get = remote_user,
        item = "PAM_RUSER",
        see = Self::remote_host
    );
    trait_item!(
        /// Sets the identity of the remote user logging in.
        ///
        /// This may be set by the application before making calls
        /// into a PAM transaction.
        set = set_remote_user,
        item = "PAM_RUSER",
        see = Self::remote_user
    );

    trait_item!(
        /// If set, the remote location where the user is coming from.
        ///
        /// This is only as trustworthy as the application calling PAM.
        /// This can be combined with [`Self::remote_user`] to identify
        /// the account the user is attempting to log in from,
        /// with `remote_user@remote_host`.
        ///
        /// If unset, "it is unclear where the authentication request
        /// is originating from."
        get = remote_host,
        item = "PAM_RHOST",
        see = Self::remote_user
    );
    trait_item!(
        /// Sets the location where the user is coming from.
        ///
        /// This may be set by the application before making calls
        /// into a PAM transaction.
        set = set_remote_host,
        item = "PAM_RHOST",
        see = Self::remote_host
    );

    trait_item!(
        /// Gets the user's authentication token (e.g., password).
        ///
        /// This is usually set automatically when
        /// [`authtok`](PamHandleModule::authtok) is called,
        /// but can be manually set.
        set = set_authtok_item,
        item = "PAM_AUTHTOK",
        see = PamHandleModule::authtok_item
    );

    trait_item!(
        /// Sets the user's "old authentication token" when changing passwords.
        ///
        /// This is usually set automatically by PAM.
        set = set_old_authtok_item,
        item = "PAM_OLDAUTHTOK",
        see = PamHandleModule::old_authtok_item
    );
}

/// 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 PamHandleApplication: 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: Flags) -> 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: Flags) -> 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: Flags) -> 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 PamHandleModule: Conversation + PamShared {
    /// Retrieves the authentication token from the user.
    ///
    /// This should only be used by *authentication* and *password-change*
    /// PAM modules.
    ///
    /// # References
    ///
    #[doc = linklist!(pam_get_authtok: man7, manbsd)]
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nonstick::handle::PamHandleModule;
    /// # fn _doc(handle: &mut impl PamHandleModule) -> 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!"))?;
    /// Ok(())
    /// # }
    /// ```
    #[doc = man7!(3 pam_get_authtok)]
    #[doc = manbsd!(3 pam_get_authtok)]
    fn authtok(&mut self, prompt: Option<&str>) -> Result<String>;

    /// Retrieves the user's old authentication token when changing passwords.
    ///
    ///
    fn old_authtok(&mut self, prompt: Option<&str>) -> Result<String>;

    trait_item!(
        /// Gets the user's authentication token (e.g., password).
        ///
        /// This is normally set automatically by PAM when calling
        /// [`authtok`](Self::authtok), but can be set explicitly.
        ///
        /// Like `authtok`, this should only ever be called
        /// by *authentication* and *password-change* PAM modules.
        get = authtok_item,
        item = "PAM_AUTHTOK",
        see = Self::authtok
    );

    trait_item!(
        /// Gets the user's old authentication token when changing passwords.
        ///
        /// This is normally set automatically by PAM when calling
        /// [`old_authtok`](Self::old_authtok), but can be set explicitly.
        ///
        /// This should only ever be called by *password-change* PAM modules.
        get = old_authtok_item,
        item = "PAM_OLDAUTHTOK",
        see = PamShared::set_old_authtok_item
    );
}