view 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 source

//! The wrapper types and traits for handles into the PAM library.
use crate::constants::Result;
use crate::conv::Conversation;

macro_rules! trait_item {
    (get = $getter:ident, item = $item:literal $(, see = $see:path)? $(, $($doc:literal)*)?) => {
        $(
            $(#[doc = $doc])*
            #[doc = ""]
        )?
        #[doc = concat!("Gets the `", $item, "` of the PAM handle.")]
        $(
            #[doc = concat!("See [`", stringify!($see), "`].")]
        )?
        #[doc = ""]
        #[doc = "Returns a reference to the item's value, owned by PAM."]
        #[doc = "The item is assumed to be valid UTF-8 text."]
        #[doc = "If it is not, `ConversationError` is returned."]
        #[doc = ""]
        #[doc = "See the [`pam_get_item`][man] manual page,"]
        #[doc = "[`pam_get_item` in the Module Writers' Guide][mwg], or"]
        #[doc = "[`pam_get_item` in the Application Developers' Guide][adg]."]
        #[doc = ""]
        #[doc = "[man]: https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html"]
        #[doc = "[adg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/adg-interface-by-app-expected.html#adg-pam_get_item"]
        #[doc = "[mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item"]
        fn $getter(&mut self) -> Result<Option<&str>>;
    };
    (set = $setter:ident, item = $item:literal $(, see = $see:path)? $(, $($doc:literal)*)?) => {
        $(
            $(#[doc = $doc])*
            #[doc = ""]
        )?
        #[doc = concat!("Sets the `", $item, "` from the PAM handle.")]
        $(
            #[doc = concat!("See [`", stringify!($see), "`].")]
        )?
        #[doc = ""]
        #[doc = "Sets the item's value. PAM copies the string's contents."]
        #[doc = "If the string contains a null byte, this will return "]
        #[doc = "a `ConversationError`."]
        #[doc = ""]
        #[doc = "See the [`pam_set_item`][man] manual page,"]
        #[doc = "[`pam_set_item` in the Module Writers' Guide][mwg], or"]
        #[doc = "[`pam_set_item` in the Application Developers' Guide][adg]."]
        #[doc = ""]
        #[doc = "[man]: https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html"]
        #[doc = "[adg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/adg-interface-by-app-expected.html#adg-pam_set_item"]
        #[doc = "[mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_item"]
        fn $setter(&mut self, value: Option<&str>) -> Result<()>;
    };
}

/// 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.
///
/// 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;
    /// 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: `.
    ///
    /// See the [`pam_get_user` manual page][man]
    /// or [`pam_get_user` in the Module Writer's Guide][mwg].
    ///
    /// # 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.get_user(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.get_user(Some("who ARE you even???"))?;
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// [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<&str>;

    trait_item!(
        get = user_item,
        item = "PAM_USER",
        see = Self::get_user,
        "The identity of the user for whom service is being requested."
        ""
        "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!(
        set = set_user_item,
        item = "PAM_USER",
        see = Self::user_item,
        "Sets the identity of the logging-in user."
        ""
        "Usually this will be set during the course of "
        "a [`get_user`](Self::get_user) call, but you may set it manually "
        "or change it during the PAM process."
    );

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

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

    trait_item!(
        get = tty_name,
        item = "PAM_TTY",
        "\"The terminal name prefixed by /dev/ for device files.\""
        ""
        "This is the terminal the user is logging in on."
        "Very old applications may use this instead of `PAM_XDISPLAY`."
    );
    trait_item!(
        set = set_tty_name,
        item = "PAM_TTY",
        see = Self::tty_name,
        "Sets the terminal name."
        ""
        "(TODO: See if libpam sets this itself or if the application does.)"
    );

    trait_item!(
        get = remote_user,
        item = "PAM_RUSER",
        "If set, the identity of the remote user logging in."
        ""
        "This is only as trustworthy as the application calling PAM."
        "Also see [`remote_host`](Self::remote_host)."
    );
    trait_item!(
        set = set_remote_user,
        item = "PAM_RUSER",
        "Sets the identity of the remote user logging in."
        ""
        "This is usually set by the application before making calls "
        "into a PAM session. (TODO: check this!)"
    );

    trait_item!(
        get = remote_host,
        item = "PAM_RHOST",
        "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.\""
    );
    trait_item!(
        set = set_remote_host,
        item = "PAM_RHOST",
        see = Self::remote_host,
        "Sets the location where the user is coming from."
        ""
        "This is usually set by the application before making calls "
        "into a PAM session. (TODO: check this!)"
    );

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

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

/// 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 PamApplicationOnly {
    /// Closes the PAM session on an owned PAM handle.
    ///
    /// This should be called with the result of the application's last call
    /// into PAM services. Since this is only applicable to *owned* PAM handles,
    /// a PAM module should never call this (and it will never be handed
    /// an owned `PamHandle` that it can `close`).
    ///
    /// See the [`pam_end` manual page][man] for more information.
    ///
    /// ```no_run
    /// # use nonstick::handle::PamApplicationOnly;
    /// # use std::error::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)?;
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// [man]: https://www.man7.org/linux/man-pages/man3/pam_end.3.html
    fn close(self, status: Result<()>) -> 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 PamModuleOnly: Conversation {
    /// Retrieves the authentication token from the user.
    ///
    /// This should only be used by *authentication* and *password-change*
    /// PAM modules.
    ///
    /// See the [`pam_get_authtok` manual page][man]
    /// or [`pam_get_item` in the Module Writer's Guide][mwg].
    ///
    /// # Example
    ///
    /// ```no_run
    /// # 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.
    /// let pass = handle.get_authtok(Some("Reveal your secrets!"))?;
    /// Ok(())
    /// # }
    /// ```
    ///
    /// [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<&str>;

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

    trait_item!(
        get = old_authtok_item,
        item = "PAM_OLDAUTHTOK",
        see = PamHandle::set_old_authtok_item,
        "Gets the user's old authentication token when changing passwords."
        ""
        "This should only ever be called by *password-change* PAM modules."
    );

    /*
    TODO: Re-enable this at some point.
        /// Gets some pointer, identified by `key`, that has been set previously
        /// using [`set_data`](Self::set_data).
        ///
        /// The data, if present, is still owned by the current PAM session.
        ///
        /// See the [`pam_get_data` manual page][man]
        /// or [`pam_get_data` in the Module Writer's Guide][mwg].
        ///
        /// # Safety
        ///
        /// The data stored under the provided key must be of type `T`,
        /// otherwise you'll get back a completely invalid `&T`
        /// and further behavior is undefined.
        ///
        /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html
        /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_data
        unsafe fn get_data<T>(&mut self, key: &str) -> Result<Option<&T>>;

        /// Stores a pointer that can be retrieved later with [`get_data`](Self::get_data).
        ///
        /// This data is accessible to this module and other PAM modules
        /// (using the provided `key`), but is *not* accessible to the application.
        /// The PAM session takes ownership of the data, and it will be dropped
        /// when the session ends.
        ///
        /// See the [`pam_set_data` manual page][man]
        /// or [`pam_set_data` in the Module Writer's Guide][mwg].
        ///
        /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html
        /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_data
        fn set_data<T>(&mut self, key: &str, data: Box<T>) -> Result<()>;
     */
}