Mercurial > crates > nonstick
changeset 103:dfcd96a74ac4
write a truly prodigious amount of documentation
adds a bunch of links to the OpenPAM man pages and the XSSO spec
as well as just a bunch of prose and stuff.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Wed, 25 Jun 2025 00:59:24 -0400 |
parents | 94eb11cb1798 |
children | a2676475e86b |
files | src/_doc.rs src/constants.rs src/handle.rs src/lib.rs src/libpam/environ.rs src/libpam/handle.rs src/libpam/module.rs src/libpam/pam_ffi.rs src/logging.rs |
diffstat | 9 files changed, 290 insertions(+), 55 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/_doc.rs Wed Jun 25 00:59:24 2025 -0400 @@ -0,0 +1,165 @@ +//! A place to stick documentation stuff. + +/// Generates text lists of reference links for docs. +/// +/// Use this with the other doc macros for the correct link names. +/// +/// # Examples +/// +/// ``` +/// #[doc = _linklist!(pam_get_authtok: man7, manbsd)] +/// /// +/// /// ...use it with link references, like the below... +/// /// +/// #[doc = _stdlinks!(3 pam_get_authtok)] +/// ``` +#[macro_export] +#[doc(hidden)] +macro_rules! _linklist { + ($func:ident: adg$(, $rest:ident)*) => { + concat!( + "- [Application Developers' Guide on `", stringify!($func), "`][adg]\n", + $crate::_linklist!($func: $($rest),*) + ) + }; + ($func:ident: mwg$(, $rest:ident)*) => { + concat!( + "- [Module Writers' Guide on `", stringify!($func), "`][mwg]\n", + $crate::_linklist!($func: $($rest),*) + ) + }; + ($func:ident: _std$(, $rest:ident)*) => { + $crate::_linklist!($func: man7, manbsd, xsso$(, $rest)*) + }; + ($func:ident: man7$(, $rest:ident)*) => { + concat!( + "- [Linux-PAM manpage for `", stringify!($func), "`][man7]\n", + $crate::_linklist!($func: $($rest),*) + ) + }; + ($func:ident: manbsd$(, $rest:ident)*) => { + concat!( + "- [OpenPAM manpage for `", stringify!($func), "`][manbsd]\n", + $crate::_linklist!($func: $($rest),*) + ) + }; + ($func:ident: xsso$(, $rest:ident)*) => { + concat!( + "- [X/SSO spec for `", stringify!($func), "`][xsso]", + $crate::_linklist!($func: $($rest),*) + ) + }; + ($func:ident:$(,)?) => { "" }; +} + +/// Generates a Markdown link reference to one of the PAM guides. +/// +/// # Examples +/// +/// ``` +/// #[doc = _guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_get_user")] +/// ``` +#[macro_export] +#[doc(hidden)] +macro_rules! _guide { + ($name:ident: $page_link:literal) => { + concat!( + "[", + stringify!($name), + "]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/", + $page_link + ) + }; +} + +/// Generates a Markdown link reference to the Linux man pages on man7.org. +/// +/// # Examples +/// +/// ``` +/// // Both of these formulations create a link reference named `man7`. +/// #[doc = _man7!(3 fn_name)] +/// #[doc = _man7!(5 thing_name "SECTION")] +/// // This one creates a link reference named `link_name`. +/// #[doc = _man7!(link_name: 1 prog_name "SECTION")] +/// ``` +#[macro_export] +#[doc(hidden)] +macro_rules! _man7 { + ($n:literal $fn:ident $($anchor:literal)?) => { + $crate::_man7!(man7: $n $fn $($anchor)?) + }; + ($name:ident: $n:literal $fn:ident $($anchor:literal)?) => { + concat!( + "[", stringify!($name), "]: ", + "https://man7.org/linux/man-pages/man", $n, "/", + stringify!($fn), ".", $n, ".html", $("#", $anchor)? + ) + }; +} + +/// Generates a Markdown link reference to the NetBSD man pages. +/// +/// # Examples +/// +/// ``` +/// // Both of these formulations create a link named `manbsd`. +/// #[doc = _manbsd!(3 fn_name)] +/// #[doc = _manbsd!(5 thing_name "SECTION")] +/// // This one creates a link named `link_name`. +/// #[doc = _manbsd!(link_name: 1 prog_name "SECTION")] +/// ``` +#[macro_export] +#[doc(hidden)] +macro_rules! _manbsd { + ($n:literal $func:ident $($anchor:literal)?) => { + $crate::_manbsd!(manbsd: $n $func $($anchor)?) + }; + ($name:ident: $n:literal $func:ident $($anchor:literal)?) => { + concat!("[", stringify!($name), "]: ", + "https://man.netbsd.org/", stringify!($func), ".", $n, + $("#", $anchor)? + ) + }; +} + +/// Generates a Markdown link reference to the X/SSO specification. +/// +/// # Examples +/// +/// ``` +/// // Both of these formulations create a link reference named `xsso`. +/// // A link to the X/SSO specification for the `pam_set_item` function. +/// #[doc = _xsso!(pam_set_item)] +/// // A link to the HTML page with the given name. +/// #[doc = _xsso!("some_page.htm#section-id")] +/// +/// // This one creates a link reference named `spec_toc`. +/// #[doc = _xsso!(spec_toc: "toc.htm")] +/// ``` +#[macro_export] +#[doc(hidden)] +macro_rules! _xsso { + ($func:ident) => { $crate::_xsso!(xsso: concat!(stringify!($func), ".htm")) }; + ($page:literal) => { $crate::_xsso!(xsso: $page) }; + ($name:ident: $page:expr) => { + concat!("[", stringify!($name), "]: https://pubs.opengroup.org/onlinepubs/8329799/", $page) + }; +} + +/// Generates Markdown link references to Linux-PAM, OpenPAM, and X/SSO. +/// +/// A shortcut to `_man7!`, `_manbsd!`, and `_xsso!`. +/// +/// # Examples +/// +/// ``` +/// #[doc = _stdlinks!(3 pam_get_item)] +/// ``` +#[macro_export] +#[doc(hidden)] +macro_rules! _stdlinks { + ($n:literal $func:ident) => { + concat!($crate::_man7!($n $func), "\n", $crate::_manbsd!($n $func), "\n", $crate::_xsso!($func)) + }; +}
--- a/src/constants.rs Tue Jun 24 18:11:38 2025 -0400 +++ b/src/constants.rs Wed Jun 25 00:59:24 2025 -0400 @@ -6,6 +6,7 @@ #[cfg(feature = "link")] use crate::libpam::pam_ffi; +use crate::{_linklist, _man7, _manbsd, _xsso}; use bitflags::bitflags; use libc::c_int; use num_enum::{IntoPrimitive, TryFromPrimitive}; @@ -145,11 +146,20 @@ } } -/// The Linux-PAM error return values. Success is an Ok [Result]. +/// The PAM error return codes. +/// +/// These are returned by most PAM functions if an error of some kind occurs. +/// +/// Instead of being an error code, success is represented by an Ok [`Result`]. /// -/// Most abbreviations (except `AuthTok` and `Max`) are now full words. -/// For more detailed information, see -/// `/usr/include/security/_pam_types.h`. +/// # References +/// +#[doc = _linklist!(pam: man7, manbsd)] +/// - [X/SSO error code specification][xsso] +/// +#[doc = _man7!(3 pam "RETURN_VALUES")] +#[doc = _manbsd!(3 pam "RETURN%20VALUES")] +#[doc = _xsso!("chap5.htm#tagcjh_06_02")] #[allow(non_camel_case_types, dead_code)] #[derive(Copy, Clone, Debug, PartialEq, TryFromPrimitive, IntoPrimitive)] #[non_exhaustive] // C might give us anything! @@ -228,9 +238,6 @@ } } -/// Returned when text that should not have any `\0` bytes in it does. -/// Analogous to [`std::ffi::NulError`], but the data it was created from -/// is borrowed. #[cfg(test)] mod tests { use super::*;
--- a/src/handle.rs Tue Jun 24 18:11:38 2025 -0400 +++ b/src/handle.rs Wed Jun 25 00:59:24 2025 -0400 @@ -4,6 +4,7 @@ use crate::conv::Conversation; use crate::environ::{EnvironMap, EnvironMapMut}; use crate::logging::Level; +use crate::{_guide, _linklist, _man7, _manbsd, _stdlinks}; macro_rules! trait_item { ($(#[$md:meta])* get = $getter:ident, item = $item:literal $(, see = $see:path)?) => { @@ -18,17 +19,18 @@ /// The item is assumed to be valid UTF-8 text. /// If it is not, `ConversationError` is returned. /// - /// See the [`pam_get_item`][man] manual page, - /// [`pam_get_item` in the Module Writers' Guide][mwg], or - /// [`pam_get_item` in the Application Developers' Guide][adg]. + /// # References + /// + #[doc = _linklist!(pam_get_item: mwg, adg, _std)] /// - /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html - /// [adg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/adg-interface-by-app-expected.html#adg-pam_get_item - /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item + #[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), "`].")] @@ -38,13 +40,13 @@ /// If the string contains a null byte, this will return /// a `ConversationError`. /// - /// See the [`pam_set_item`][man] manual page, - /// [`pam_set_item` in the Module Writers' Guide][mwg], or - /// [`pam_set_item` in the Application Developers' Guide][adg]. + /// # References + /// + #[doc = _linklist!(pam_set_item: mwg, adg, _std)] /// - /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html - /// [adg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/adg-interface-by-app-expected.html#adg-pam_set_item - /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_item + #[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<()>; }; } @@ -61,9 +63,14 @@ /// Logs something via this PAM handle. /// /// You probably want to use one of the logging macros, - /// like [`error!`], [`warning!`], [`info!`], or [`debug!`]. + /// 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 /// @@ -82,6 +89,8 @@ /// pam_hdl.log(Level::Warning, "this is unnecessarily verbose"); /// # } /// ``` + #[doc = _man7!(3 pam_syslog)] + #[doc = _manbsd!(3 openpam_log)] fn log(&self, level: Level, entry: &str); /// Retrieves the name of the user who is authenticating or logging in. @@ -93,8 +102,8 @@ /// 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]. + /// # References + #[doc = _linklist!(pam_get_user: mwg, _std)] /// /// # Example /// @@ -110,9 +119,8 @@ /// # 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 + #[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. @@ -151,8 +159,7 @@ item = "PAM_SERVICE" ); trait_item!( - /// The service name, which identifies the PAM stack which is used - /// to perform authentication. It's probably a bad idea to change this. + /// Sets the service name. It's probably a bad idea to change this. set = set_service, item = "PAM_SERVICE", see = Self::service @@ -191,9 +198,9 @@ /// 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). get = remote_user, - item = "PAM_RUSER" + item = "PAM_RUSER", + see = Self::remote_host ); trait_item!( /// Sets the identity of the remote user logging in. @@ -201,7 +208,8 @@ /// This may be set by the application before making calls /// into a PAM transaction. set = set_remote_user, - item = "PAM_RUSER" + item = "PAM_RUSER", + see = Self::remote_user ); trait_item!( @@ -215,7 +223,8 @@ /// If unset, "it is unclear where the authentication request /// is originating from." get = remote_host, - item = "PAM_RHOST" + item = "PAM_RHOST", + see = Self::remote_user ); trait_item!( /// Sets the location where the user is coming from. @@ -240,7 +249,7 @@ 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", @@ -257,12 +266,42 @@ /// 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<()>; - /// Does "account management". + /// 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<()>; } @@ -277,10 +316,12 @@ /// Retrieves the authentication token from the user. /// /// This should only be used by *authentication* and *password-change* - /// PAM modules. + /// PAM modules. This is an extension provided by + /// both Linux-PAM and OpenPAM. /// - /// See the [`pam_get_authtok` manual page][man] - /// or [`pam_get_item` in the Module Writer's Guide][mwg]. + /// # References + /// + #[doc = _linklist!(pam_get_authtok: man7, manbsd)] /// /// # Example /// @@ -294,9 +335,8 @@ /// 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 + #[doc = _man7!(3 pam_get_authtok)] + #[doc = _manbsd!(3 pam_get_authtok)] fn authtok(&mut self, prompt: Option<&str>) -> Result<String>; trait_item!(
--- a/src/lib.rs Tue Jun 24 18:11:38 2025 -0400 +++ b/src/lib.rs Wed Jun 25 00:59:24 2025 -0400 @@ -35,6 +35,7 @@ #[cfg(feature = "link")] mod libpam; pub mod logging; +mod _doc; #[cfg(feature = "link")] #[doc(inline)]
--- a/src/libpam/environ.rs Tue Jun 24 18:11:38 2025 -0400 +++ b/src/libpam/environ.rs Wed Jun 25 00:59:24 2025 -0400 @@ -1,4 +1,3 @@ -#![allow(unused_variables)] // for now use crate::constants::{ErrorCode, Result}; use crate::environ::{EnvironMap, EnvironMapMut}; use crate::libpam::memory::CHeapString; @@ -31,6 +30,11 @@ fn environ_set(&mut self, key: &OsStr, value: Option<&OsStr>) -> Result<Option<OsString>> { let old = self.environ_get(key); + if old.is_none() && value.is_none() { + // pam_putenv returns an error if we try to remove a non-existent + // environment variable, so just avoid that entirely. + return Ok(None) + } let total_len = key.len() + value.map(OsStr::len).unwrap_or_default() + 2; let mut result = Vec::with_capacity(total_len); result.extend(key.as_bytes()); @@ -45,7 +49,7 @@ } fn environ_iter(&self) -> Result<impl Iterator<Item = (OsString, OsString)>> { - // SAFETY: This is a valid PAM handle. + // SAFETY: This is a valid PAM handle. It will return valid data. unsafe { NonNull::new(pam_ffi::pam_getenvlist( (self as *const LibPamHandle).cast_mut(),
--- a/src/libpam/handle.rs Tue Jun 24 18:11:38 2025 -0400 +++ b/src/libpam/handle.rs Wed Jun 25 00:59:24 2025 -0400 @@ -7,7 +7,10 @@ pub use crate::libpam::pam_ffi::LibPamHandle; use crate::libpam::{memory, pam_ffi}; use crate::logging::Level; -use crate::{Conversation, EnvironMap, Flags, PamHandleApplication, PamHandleModule}; +use crate::{ + Conversation, EnvironMap, Flags, PamHandleApplication, PamHandleModule, _guide, _linklist, + _stdlinks, +}; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::cell::Cell; use std::ffi::{c_char, c_int, CString}; @@ -78,13 +81,16 @@ /// when authenticating a user. This corresponds to the configuration file /// named <code>/etc/pam.d/<var>service_name</var></code>. /// - /// For more information, see the [`pam_start` man page][man], or - /// [`pam_start` in the PAM Application Developers' Guide][adg]. + /// # References + #[doc = _linklist!(pam_start: adg, _std)] /// - /// [man]: https://www.man7.org/linux/man-pages/man3/pam_start.3.html - /// [adg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/adg-interface-by-app-expected.html#adg-pam_start + #[doc = _stdlinks!(3 pam_start)] + #[doc = _guide!(adg: "adg-interface-by-app-expected.html#adg-pam_start")] pub fn build_with_service(service_name: String) -> HandleBuilder { - HandleBuilder { service_name, username: None } + HandleBuilder { + service_name, + username: None, + } } fn start( @@ -152,9 +158,13 @@ impl Drop for OwnedLibPamHandle<'_> { /// Closes the PAM session on an owned PAM handle. /// - /// See the [`pam_end` manual page][man] for more information. + /// This internally calls `pam_end` with the appropriate error code. /// - /// [man]: https://www.man7.org/linux/man-pages/man3/pam_end.3.html + /// # References + #[doc = _linklist!(pam_end: adg, _std)] + /// + #[doc = _guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")] + #[doc = _stdlinks!(3 pam_end)] fn drop(&mut self) { unsafe { pam_ffi::pam_end(
--- a/src/libpam/module.rs Tue Jun 24 18:11:38 2025 -0400 +++ b/src/libpam/module.rs Wed Jun 25 00:59:24 2025 -0400 @@ -11,8 +11,8 @@ /// /// ```no_run /// use nonstick::{ -/// pam_hooks, Flags, OwnedLibPamHandle, PamHandleModule, PamModule, Result as PamResult, -/// ConversationAdapter, +/// pam_hooks, ConversationAdapter, Flags, OwnedLibPamHandle, PamHandleModule, PamModule, +/// Result as PamResult, /// }; /// use std::ffi::CStr; /// # fn main() {}
--- a/src/libpam/pam_ffi.rs Tue Jun 24 18:11:38 2025 -0400 +++ b/src/libpam/pam_ffi.rs Wed Jun 25 00:59:24 2025 -0400 @@ -29,7 +29,8 @@ #[derive(Debug, Default)] pub struct Answer { /// Owned pointer to the data returned in an answer. - /// For most answers, this will be a [`CHeapString`], + /// For most answers, this will be a + /// [`CHeapString`](crate::libpam::memory::CHeapString), /// but for [`BinaryQAndA`](crate::conv::BinaryQAndA)s /// (a Linux-PAM extension), this will be a [`CHeapBox`] of /// [`CBinaryData`](crate::libpam::memory::CBinaryData).
--- a/src/logging.rs Tue Jun 24 18:11:38 2025 -0400 +++ b/src/logging.rs Wed Jun 25 00:59:24 2025 -0400 @@ -78,7 +78,10 @@ /// ```no_run /// # fn _test(pam_handle: impl nonstick::PamShared) { /// # let load_error = "xxx"; -/// nonstick::error!(pam_handle, "error loading data from data source: {load_error}"); +/// nonstick::error!( +/// pam_handle, +/// "error loading data from data source: {load_error}" +/// ); /// // Will log a message like "error loading data from data source: timed out" /// // at ERROR level on syslog. /// # } @@ -94,8 +97,12 @@ /// /// ```no_run /// # fn _test(pam_handle: impl nonstick::PamShared) { -/// # let latency_ms = "xxx"; -/// nonstick::warn!(pam_handle, "loading took too long: {latency_ms} ms"); +/// # let (start, finish) = (0, 0); +/// nonstick::warn!( +/// pam_handle, +/// "loading took too long: {latency_ms} ms", +/// latency_ms = start - finish +/// ); /// // Will log a message like "loading took too long: 495 ms" /// // at WARN level on syslog. /// # }