# HG changeset patch # User Paul Fisher # Date 1751843426 14400 # Node ID 1bc52025156bc552bcc3fc10d9e07413a2d28eac # Parent 8f964b7016521bd7306590058774f8c9599f903f Split PAM items into their own separate struct. To trim down the number of methods on `PamShared`, this puts all the Items into their own struct(s). This also makes the split between authtok/authtok_item easier to understand. diff -r 8f964b701652 -r 1bc52025156b src/_doc.rs --- a/src/_doc.rs Sun Jul 06 19:04:57 2025 -0400 +++ b/src/_doc.rs Sun Jul 06 19:10:26 2025 -0400 @@ -7,7 +7,7 @@ /// # Examples /// /// ```ignore -/// # use nonstick::{linklist, stdlinks}; +/// # use nonstick::_doc::{linklist, stdlinks}; /// /// Here is a list of links: /// /// /// #[doc = linklist!(pam_get_authtok: man7, manbsd)] @@ -21,34 +21,34 @@ ($func:ident: adg$(, $rest:ident)*) => { concat!( "- [Application Developers' Guide on `", stringify!($func), "`][adg]\n", - $crate::linklist!($func: $($rest),*) + $crate::_doc::linklist!($func: $($rest),*) ) }; ($func:ident: mwg$(, $rest:ident)*) => { concat!( "- [Module Writers' Guide on `", stringify!($func), "`][mwg]\n", - $crate::linklist!($func: $($rest),*) + $crate::_doc::linklist!($func: $($rest),*) ) }; ($func:ident: _std$(, $rest:ident)*) => { - $crate::linklist!($func: man7, manbsd, xsso$(, $rest)*) + $crate::_doc::linklist!($func: man7, manbsd, xsso$(, $rest)*) }; ($func:ident: man7$(, $rest:ident)*) => { concat!( "- [Linux-PAM manpage for `", stringify!($func), "`][man7]\n", - $crate::linklist!($func: $($rest),*) + $crate::_doc::linklist!($func: $($rest),*) ) }; ($func:ident: manbsd$(, $rest:ident)*) => { concat!( "- [OpenPAM manpage for `", stringify!($func), "`][manbsd]\n", - $crate::linklist!($func: $($rest),*) + $crate::_doc::linklist!($func: $($rest),*) ) }; ($func:ident: xsso$(, $rest:ident)*) => { concat!( "- [X/SSO spec for `", stringify!($func), "`][xsso]", - $crate::linklist!($func: $($rest),*) + $crate::_doc::linklist!($func: $($rest),*) ) }; ($func:ident:$(,)?) => { "" }; @@ -81,7 +81,7 @@ /// # Examples /// /// ```ignore -/// # use nonstick::man7; +/// # use nonstick::_doc::man7; /// /// This contains a [link to the man page for malloc][man7]. /// #[doc = man7!(3 malloc)] /// # fn do_whatever() {} @@ -95,7 +95,7 @@ /// ``` macro_rules! man7 { ($n:literal $fn:ident $($anchor:literal)?) => { - $crate::man7!(man7: $n $fn $($anchor)?) + $crate::_doc::man7!(man7: $n $fn $($anchor)?) }; ($name:ident: $n:literal $fn:ident $($anchor:literal)?) => { concat!( @@ -111,7 +111,7 @@ /// # Examples /// /// ```ignore -/// # use nonstick::manbsd; +/// # use nonstick::_doc::manbsd; /// // Both of these formulations create a link named `manbsd`. /// #[doc = manbsd!(3 fn_name)] /// #[doc = manbsd!(5 thing_name "SECTION")] @@ -121,7 +121,7 @@ /// ``` macro_rules! manbsd { ($n:literal $func:ident $($anchor:literal)?) => { - $crate::manbsd!(manbsd: $n $func $($anchor)?) + $crate::_doc::manbsd!(manbsd: $n $func $($anchor)?) }; ($name:ident: $n:literal $func:ident $($anchor:literal)?) => { concat!("[", stringify!($name), "]: ", @@ -136,7 +136,7 @@ /// # Examples /// /// ```ignore -/// # use nonstick::xsso; +/// # use nonstick::_doc::xsso; /// /// This docstring will [link to the X/SSO spec for `pam_set_item`][xsso]. /// /// /// #[doc = xsso!(pam_set_item)] @@ -150,8 +150,8 @@ /// # fn do_whatever() {} /// ``` macro_rules! xsso { - ($func:ident) => { $crate::xsso!(xsso: concat!(stringify!($func), ".htm")) }; - ($page:literal) => { $crate::xsso!(xsso: $page) }; + ($func:ident) => { $crate::_doc::xsso!(xsso: concat!(stringify!($func), ".htm")) }; + ($page:literal) => { $crate::_doc::xsso!(xsso: $page) }; ($name:ident: $page:expr) => { concat!("[", stringify!($name), "]: https://pubs.opengroup.org/onlinepubs/8329799/", $page) }; @@ -164,7 +164,7 @@ /// # Examples /// /// ```ignore -/// # use nonstick::stdlinks; +/// # use nonstick::_doc::stdlinks; /// /// Check out [this][man7], [that][manbsd], or [the other][xsso]. /// /// /// #[doc = stdlinks!(3 pam_get_item)] @@ -172,7 +172,10 @@ /// ``` macro_rules! stdlinks { ($n:literal $func:ident) => { - concat!($crate::man7!($n $func), "\n", $crate::manbsd!($n $func), "\n", $crate::xsso!($func)) + concat!( + $crate::_doc::man7!($n $func), "\n", + $crate::_doc::manbsd!($n $func), "\n", + $crate::_doc::xsso!($func)) }; } diff -r 8f964b701652 -r 1bc52025156b src/constants.rs --- a/src/constants.rs Sun Jul 06 19:04:57 2025 -0400 +++ b/src/constants.rs Sun Jul 06 19:10:26 2025 -0400 @@ -1,6 +1,6 @@ //! Constants and enum values from the PAM library. -use crate::{linklist, man7, manbsd, xsso}; +use crate::_doc::{linklist, man7, manbsd, xsso}; use bitflags::bitflags; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::error::Error; diff -r 8f964b701652 -r 1bc52025156b src/conv.rs --- a/src/conv.rs Sun Jul 06 19:04:57 2025 -0400 +++ b/src/conv.rs Sun Jul 06 19:10:26 2025 -0400 @@ -412,7 +412,7 @@ }; } -impl ConversationAdapter for C { +impl ConversationAdapter for C { conv_fn!(prompt(message: OsStr) -> OsString { QAndA }); conv_fn!(masked_prompt(message: OsStr) -> OsString { MaskedQAndA } ); conv_fn!(error_msg(message: OsStr) { ErrorMsg }); diff -r 8f964b701652 -r 1bc52025156b src/handle.rs --- a/src/handle.rs Sun Jul 06 19:04:57 2025 -0400 +++ b/src/handle.rs Sun Jul 06 19:10:26 2025 -0400 @@ -1,59 +1,13 @@ //! The wrapper types and traits for handles into the PAM library. +use crate::_doc::{guide, linklist, man7, manbsd, stdlinks}; use crate::constants::{Flags, Result}; use crate::conv::Conversation; use crate::environ::{EnvironMap, EnvironMapMut}; +use crate::items::{getter, Items, ItemsMut}; use crate::logging::{Level, Location}; -use crate::{guide, linklist, man7, manbsd, stdlinks}; use std::ffi::{OsStr, OsString}; -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>; - }; - ($(#[$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. - /// - /// # Panics - /// - /// If the string contains a nul byte, this will panic. - /// - /// # 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<&OsStr>) -> Result<()>; - }; -} - /// Functionality for both PAM applications and PAM modules. /// /// This base trait includes features of a PAM handle that are available @@ -127,138 +81,53 @@ #[doc = guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_get_user")] fn username(&mut self, prompt: Option<&OsStr>) -> Result; - /// The contents of the environment to set, read-only. + /// 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 version of the environment. + /// 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; - 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 - ); + /// 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; - 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 - ); + /// 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. @@ -316,7 +185,7 @@ /// /// Like [`PamShared`], this is intended to allow creating mock implementations /// of PAM for testing PAM modules. -pub trait PamHandleModule: Conversation + PamShared { +pub trait ModuleClient: Conversation + PamShared { /// Retrieves the authentication token from the user. /// /// This should only be used by *authentication* and *password-change* @@ -329,8 +198,8 @@ /// # Example /// /// ```no_run - /// # use nonstick::handle::PamHandleModule; - /// # fn _doc(handle: &mut impl PamHandleModule) -> Result<(), Box> { + /// # use nonstick::handle::ModuleClient; + /// # fn _doc(handle: &mut impl ModuleClient) -> Result<(), Box> { /// // Get the user's password using the default prompt. /// let pass = handle.authtok(None)?; /// // Get the user's password using a custom prompt. @@ -344,31 +213,63 @@ /// 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> { + /// // 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; - trait_item!( + getter!( /// 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. + /// 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. - get = authtok_item, - item = "PAM_AUTHTOK", - see = Self::authtok + /// + /// # 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) ); - trait_item!( + getter!( /// 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 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. - get = old_authtok_item, - item = "PAM_OLDAUTHTOK", - see = PamShared::set_old_authtok_item + /// + /// # 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) ); } diff -r 8f964b701652 -r 1bc52025156b src/items.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/items.rs Sun Jul 06 19:10:26 2025 -0400 @@ -0,0 +1,169 @@ +use crate::_doc::{guide, linklist, stdlinks}; +use crate::constants::Result; +#[cfg(doc)] +use crate::handle::{ModuleClient, PamShared}; +use std::ffi::{OsStr, OsString}; + +macro_rules! getter { + ($(#[$md:meta])* $getter:ident($item:literal $(, see = $see:path)?)) => { + $(#[$md])* + #[doc = ""] + #[doc = concat!("Gets the `", $item, "` of the PAM handle.")] + $( + #[doc = concat!("See [`", stringify!($see), "`].")] + )? + fn $getter(&self) -> Result>; + }; +} + +pub(crate) use getter; +macro_rules! setter { + ($(#[$md:meta])* $setter:ident($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. + /// + /// # Panics + /// + /// If the string contains a nul byte, this will panic. + /// + fn $setter(&mut self, value: Option<&OsStr>) -> Result<()>; + }; +} + +/// Provides access to Items, pieces of data shared by the PAM application, +/// modules, and the framework itself. +/// +/// # 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)] +pub trait Items<'a> { + getter!( + /// The identity of the user for whom service is being requested. + /// + /// Unlike [`username`](PamShared::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. + user("PAM_USER", see = PamShared::username) + ); + + getter!( + /// If set, the identity of the remote user logging in. + /// + /// This is only as trustworthy as the application calling PAM. + remote_user("PAM_RUSER", see = Self::remote_host) + ); + + getter!( + /// 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." + remote_host("PAM_RHOST", see = Self::remote_user) + ); + + getter!( + /// The service name, which identifies the PAM stack which is used + /// to perform authentication. + service("PAM_SERVICE") + ); + + getter!( + /// The string used to prompt for a user's name. + /// By default, this is a localized version of `login: `. + user_prompt("PAM_USER_PROMPT") + ); + + getter!( + /// 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`. + tty_name("PAM_TTY") + ); +} + +/// Provides write access to PAM Items, data shared by the PAM application, +/// the framework, and 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)] +pub trait ItemsMut<'a>: Items<'a> { + setter!( + /// Sets the identity of the logging-in user. + /// + /// Usually this will be set during the course of + /// a [`username`](PamShared::username) call, but you may set it manually + /// or change it during the PAM process. + set_user("PAM_USER", see = Items::user) + ); + + setter!( + /// Sets the service name. It's probably a bad idea to change this. + set_service("PAM_SERVICE", see = Items::service) + ); + + setter!( + /// Sets the string used to prompt for a user's name. + set_user_prompt("PAM_USER_PROMPT", see = Items::user_prompt) + ); + + setter!( + /// Sets the path to the terminal where the user is logging on. + set_tty_name("PAM_TTY", see = Items::tty_name) + ); + + setter!( + /// Sets the identity of the remote user logging in. + /// + /// This may be set by the application before making calls + /// into a PAM transaction. + set_remote_user("PAM_RUSER", see = Items::remote_user) + ); + + setter!( + /// Sets the location where the user is coming from. + /// + /// This may be set by the application before making calls + /// into a PAM transaction. + set_remote_host("PAM_RHOST", see = Items::remote_host) + ); + + setter!( + /// Gets the user's authentication token (e.g., password). + /// + /// This is usually set automatically when + /// [`authtok`](ModuleClient::authtok) is called, + /// but can be manually set. + set_authtok("PAM_AUTHTOK", see = ModuleClient::authtok_item) + ); + setter!( + /// Sets the user's "old authentication token" when changing passwords. + /// + /// This is usually set automatically by PAM when + /// [`old_authtok`](ModuleClient::old_authtok) is called. + set_old_authtok("PAM_OLDAUTHTOK", see = ModuleClient::old_authtok_item) + ); +} diff -r 8f964b701652 -r 1bc52025156b src/lib.rs --- a/src/lib.rs Sun Jul 06 19:04:57 2025 -0400 +++ b/src/lib.rs Sun Jul 06 19:10:26 2025 -0400 @@ -32,10 +32,10 @@ pub mod handle; mod _doc; -pub(crate) use _doc::*; mod environ; +pub mod items; #[cfg(feature = "link")] -mod libpam; +pub mod libpam; pub mod logging; #[cfg(feature = "link")] @@ -46,6 +46,6 @@ constants::{ErrorCode, Flags, Result}, conv::{BinaryData, Conversation, ConversationAdapter}, environ::{EnvironMap, EnvironMapMut}, - handle::{PamHandleModule, PamShared, Transaction}, + handle::{ModuleClient, PamShared, Transaction}, module::PamModule, }; diff -r 8f964b701652 -r 1bc52025156b src/libpam/handle.rs --- a/src/libpam/handle.rs Sun Jul 06 19:04:57 2025 -0400 +++ b/src/libpam/handle.rs Sun Jul 06 19:10:26 2025 -0400 @@ -1,14 +1,15 @@ use super::conversation::{OwnedConversation, PamConv}; +use crate::_doc::{guide, linklist, stdlinks}; use crate::constants::{ErrorCode, Result}; use crate::conv::Exchange; use crate::environ::EnvironMapMut; use crate::handle::PamShared; +use crate::items::{Items, ItemsMut}; use crate::libpam::environ::{LibPamEnviron, LibPamEnvironMut}; -use crate::libpam::memory; +use crate::libpam::items::{LibPamItems, LibPamItemsMut}; +use crate::libpam::{items, memory}; use crate::logging::{Level, Location}; -use crate::{ - guide, linklist, stdlinks, Conversation, EnvironMap, Flags, PamHandleModule, Transaction, -}; +use crate::{Conversation, EnvironMap, Flags, ModuleClient, Transaction}; use libpam_sys_helpers::constants; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::cell::Cell; @@ -209,28 +210,8 @@ delegate!(fn environ(&self) -> impl EnvironMap); delegate!(fn environ_mut(&mut self) -> impl EnvironMapMut); delegate!(fn username(&mut self, prompt: Option<&OsStr>) -> Result); - delegate!(get = user_item, set = set_user_item); - delegate!(get = service, set = set_service); - delegate!(get = user_prompt, set = set_user_prompt); - delegate!(get = tty_name, set = set_tty_name); - delegate!(get = remote_user, set = set_remote_user); - delegate!(get = remote_host, set = set_remote_host); - delegate!(set = set_authtok_item); - delegate!(set = set_old_authtok_item); -} - -/// Macro to implement getting/setting a CStr-based item. -macro_rules! cstr_item { - (get = $getter:ident, item = $item_type:path) => { - fn $getter(&self) -> Result> { - unsafe { self.get_cstr_item($item_type) } - } - }; - (set = $setter:ident, item = $item_type:path) => { - fn $setter(&mut self, value: Option<&OsStr>) -> Result<()> { - unsafe { self.set_cstr_item($item_type, value) } - } - }; + delegate!(fn items(&self) -> impl Items); + delegate!(fn items_mut(&mut self) -> impl ItemsMut); } /// An owned variation of a basic PAM handle. @@ -377,20 +358,13 @@ LibPamEnvironMut::new(self) } - cstr_item!(get = user_item, item = ItemType::User); - cstr_item!(set = set_user_item, item = ItemType::User); - cstr_item!(get = service, item = ItemType::Service); - cstr_item!(set = set_service, item = ItemType::Service); - cstr_item!(get = user_prompt, item = ItemType::UserPrompt); - cstr_item!(set = set_user_prompt, item = ItemType::UserPrompt); - cstr_item!(get = tty_name, item = ItemType::Tty); - cstr_item!(set = set_tty_name, item = ItemType::Tty); - cstr_item!(get = remote_user, item = ItemType::RemoteUser); - cstr_item!(set = set_remote_user, item = ItemType::RemoteUser); - cstr_item!(get = remote_host, item = ItemType::RemoteHost); - cstr_item!(set = set_remote_host, item = ItemType::RemoteHost); - cstr_item!(set = set_authtok_item, item = ItemType::AuthTok); - cstr_item!(set = set_old_authtok_item, item = ItemType::OldAuthTok); + fn items(&self) -> impl Items { + LibPamItems(self) + } + + fn items_mut(&mut self) -> impl ItemsMut { + LibPamItemsMut(self) + } } impl Conversation for LibPamHandle { @@ -406,7 +380,7 @@ } } -impl PamHandleModule for LibPamHandle { +impl ModuleClient for LibPamHandle { fn authtok(&mut self, prompt: Option<&OsStr>) -> Result { self.get_authtok(prompt, ItemType::AuthTok) } @@ -415,8 +389,12 @@ self.get_authtok(prompt, ItemType::OldAuthTok) } - cstr_item!(get = authtok_item, item = ItemType::AuthTok); - cstr_item!(get = old_authtok_item, item = ItemType::OldAuthTok); + fn authtok_item(&self) -> Result> { + unsafe { items::get_cstr_item(self, ItemType::AuthTok) } + } + fn old_authtok_item(&self) -> Result> { + unsafe { items::get_cstr_item(self, ItemType::OldAuthTok) } + } } /// Function called at the end of a PAM session that is called to clean up @@ -454,36 +432,6 @@ Err(ErrorCode::ConversationError) } - /// Gets a C string item. - /// - /// # Safety - /// - /// You better be requesting an item which is a C string. - unsafe fn get_cstr_item(&self, item_type: ItemType) -> Result> { - let mut output = ptr::null(); - let ret = - unsafe { libpam_sys::pam_get_item(self.raw_ref(), item_type as c_int, &mut output) }; - ErrorCode::result_from(ret)?; - Ok(memory::copy_pam_string(output.cast())) - } - - /// Sets a C string item. - /// - /// # Safety - /// - /// You better be setting an item which is a C string. - unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&OsStr>) -> Result<()> { - let data_str = memory::option_cstr_os(data); - let ret = unsafe { - libpam_sys::pam_set_item( - self.raw_mut(), - item_type as c_int, - memory::prompt_ptr(data_str.as_deref()).cast(), - ) - }; - ErrorCode::result_from(ret) - } - /// Gets the `PAM_CONV` item from the handle. fn conversation_item(&self) -> Result<&PamConv> { let output: *const PamConv = ptr::null_mut(); diff -r 8f964b701652 -r 1bc52025156b src/libpam/items.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/libpam/items.rs Sun Jul 06 19:10:26 2025 -0400 @@ -0,0 +1,90 @@ +use crate::constants::ErrorCode; +use crate::constants::Result; +use crate::items::{Items, ItemsMut}; +use crate::libpam::handle::ItemType; +use crate::libpam::handle::LibPamHandle; +use crate::libpam::memory; +use std::ffi::{c_int, OsStr, OsString}; +use std::ptr; + +pub struct LibPamItems<'a>(pub &'a LibPamHandle); +pub struct LibPamItemsMut<'a>(pub &'a mut LibPamHandle); + +/// Macro to implement getting/setting a CStr-based item. +macro_rules! cstr_item { + (get = $getter:ident, item = $item_type:path) => { + fn $getter(&self) -> Result> { + unsafe { get_cstr_item(&self.0, $item_type) } + } + }; + (set = $setter:ident, item = $item_type:path) => { + fn $setter(&mut self, value: Option<&OsStr>) -> Result<()> { + unsafe { set_cstr_item(&mut self.0, $item_type, value) } + } + }; +} + +impl Items<'_> for LibPamItems<'_> { + cstr_item!(get = user, item = ItemType::User); + cstr_item!(get = service, item = ItemType::Service); + cstr_item!(get = user_prompt, item = ItemType::UserPrompt); + cstr_item!(get = tty_name, item = ItemType::Tty); + cstr_item!(get = remote_user, item = ItemType::RemoteUser); + cstr_item!(get = remote_host, item = ItemType::RemoteHost); +} + +impl Items<'_> for LibPamItemsMut<'_> { + cstr_item!(get = user, item = ItemType::User); + cstr_item!(get = service, item = ItemType::Service); + cstr_item!(get = user_prompt, item = ItemType::UserPrompt); + cstr_item!(get = tty_name, item = ItemType::Tty); + cstr_item!(get = remote_user, item = ItemType::RemoteUser); + cstr_item!(get = remote_host, item = ItemType::RemoteHost); +} + +impl ItemsMut<'_> for LibPamItemsMut<'_> { + cstr_item!(set = set_user, item = ItemType::User); + cstr_item!(set = set_service, item = ItemType::Service); + cstr_item!(set = set_user_prompt, item = ItemType::UserPrompt); + cstr_item!(set = set_tty_name, item = ItemType::Tty); + cstr_item!(set = set_remote_user, item = ItemType::RemoteUser); + cstr_item!(set = set_remote_host, item = ItemType::RemoteHost); + cstr_item!(set = set_authtok, item = ItemType::AuthTok); + cstr_item!(set = set_old_authtok, item = ItemType::OldAuthTok); +} + +/// Gets a C string item. +/// +/// # Safety +/// +/// You better be requesting an item which is a C string. +pub unsafe fn get_cstr_item( + hdl: &LibPamHandle, + item_type: ItemType, +) -> crate::Result> { + let mut output = ptr::null(); + let ret = unsafe { libpam_sys::pam_get_item(hdl.raw_ref(), item_type as c_int, &mut output) }; + ErrorCode::result_from(ret)?; + Ok(memory::copy_pam_string(output.cast())) +} + +/// Sets a C string item. +/// +/// # Safety +/// +/// You better be setting an item which is a C string. +pub unsafe fn set_cstr_item( + hdl: &mut LibPamHandle, + item_type: ItemType, + data: Option<&OsStr>, +) -> crate::Result<()> { + let data_str = memory::option_cstr_os(data); + let ret = unsafe { + libpam_sys::pam_set_item( + hdl.raw_mut(), + item_type as c_int, + memory::prompt_ptr(data_str.as_deref()).cast(), + ) + }; + ErrorCode::result_from(ret) +} diff -r 8f964b701652 -r 1bc52025156b src/libpam/mod.rs --- a/src/libpam/mod.rs Sun Jul 06 19:04:57 2025 -0400 +++ b/src/libpam/mod.rs Sun Jul 06 19:10:26 2025 -0400 @@ -10,9 +10,10 @@ mod conversation; mod environ; mod handle; +mod items; mod memory; mod module; mod question; #[doc(inline)] -pub use handle::{LibPamHandle, LibPamTransaction}; +pub use handle::{LibPamHandle, LibPamTransaction, TransactionBuilder}; diff -r 8f964b701652 -r 1bc52025156b src/libpam/module.rs --- a/src/libpam/module.rs Sun Jul 06 19:04:57 2025 -0400 +++ b/src/libpam/module.rs Sun Jul 06 19:10:26 2025 -0400 @@ -11,7 +11,7 @@ /// /// ```no_run /// use nonstick::{ -/// pam_hooks, ConversationAdapter, Flags, LibPamTransaction, PamHandleModule, PamModule, +/// pam_hooks, ConversationAdapter, Flags, LibPamTransaction, ModuleClient, PamModule, /// Result as PamResult, /// }; /// use std::ffi::CStr; @@ -20,7 +20,7 @@ /// struct MyPamModule; /// pam_hooks!(MyPamModule); /// -/// impl PamModule for MyPamModule { +/// impl PamModule for MyPamModule { /// fn authenticate(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { /// let password = handle.authtok(Some("what's your password?".as_ref()))?; /// let response = @@ -151,9 +151,9 @@ #[cfg(test)] mod tests { // Compile-time test that the `pam_hooks` macro compiles. - use crate::{PamHandleModule, PamModule}; + use crate::{ModuleClient, PamModule}; struct Foo; - impl PamModule for Foo {} + impl PamModule for Foo {} pam_hooks!(Foo); } diff -r 8f964b701652 -r 1bc52025156b src/module.rs --- a/src/module.rs Sun Jul 06 19:04:57 2025 -0400 +++ b/src/module.rs Sun Jul 06 19:10:26 2025 -0400 @@ -4,7 +4,7 @@ #![allow(dead_code)] use crate::constants::{ErrorCode, Flags, Result}; -use crate::handle::PamHandleModule; +use crate::handle::ModuleClient; use std::ffi::CStr; /// A trait for a PAM module to implement. @@ -21,7 +21,7 @@ /// [manpage]: https://www.man7.org/linux/man-pages/man3/pam.3.html /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/Linux-PAM_MWG.html #[allow(unused_variables)] -pub trait PamModule { +pub trait PamModule { // Functions for auth modules. /// Authenticate the user. @@ -29,7 +29,7 @@ /// This is probably the first thing you want to implement. /// In most cases, you will want to get the user and password, /// using [`PamShared::username`](crate::PamShared::username) - /// and [`PamHandleModule::authtok`], + /// and [`ModuleClient::authtok`], /// and verify them against something. /// /// See [the Module Writer's Guide entry for `pam_sm_authenticate`][mwg] diff -r 8f964b701652 -r 1bc52025156b testharness/src/lib.rs --- a/testharness/src/lib.rs Sun Jul 06 19:04:57 2025 -0400 +++ b/testharness/src/lib.rs Sun Jul 06 19:10:26 2025 -0400 @@ -1,12 +1,12 @@ //! The nonstick library extern crate nonstick; -use nonstick::{pam_hooks, Flags, PamHandleModule, PamModule}; +use nonstick::{pam_hooks, Flags, ModuleClient, PamModule}; use std::ffi::CStr; struct TestHarness; -impl PamModule for TestHarness { +impl PamModule for TestHarness { fn authenticate(_handle: &mut M, _args: Vec<&CStr>, _flags: Flags) -> nonstick::Result<()> { Ok(()) }