Mercurial > crates > nonstick
changeset 174:9e4ce1631bd3
Dramatically expand documentation.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Tue, 29 Jul 2025 18:58:27 -0400 |
parents | 46e8ce5cd5d1 |
children | e30775c80b49 |
files | README.md libpam-sys/libpam-sys-helpers/src/lib.rs libpam-sys/src/aliases.rs libpam-sys/src/lib.rs src/_doc.rs src/handle.rs src/items.rs src/lib.rs src/libpam/handle.rs src/libpam/module.rs src/logging.rs |
diffstat | 11 files changed, 252 insertions(+), 63 deletions(-) [+] |
line wrap: on
line diff
--- a/README.md Tue Jul 29 16:52:32 2025 -0400 +++ b/README.md Tue Jul 29 18:58:27 2025 -0400 @@ -1,24 +1,62 @@ # π³ nonstick -Nonstick lets you use PAM (Pluggable Authentication Modules) from Rust without getting stuck in unsafe code. +Nonstick lets you use PAM (Pluggable Authentication Modules) from Rust safely. +Don't worry about getting stuck on unsafe code. + +You can use nonstick for interacting with PAM from both sides: + +- PAM applications: call into PAM to authenticate users. +- PAM modules: write a backend that PAM uses for authentication. + +It supports all known PAM implementations: + +- Linux-PAM, used on most (all?) Linux distributions. +- OpenPAM, used on most BSDs, including Mac OS. +- Sun's PAM, used on Solaris and derivatives like Illumos. + +Further documentation can be found in the crate's rustdoc. ## Status -This is currently somewhat incomplete. +- **Modules**: full support for all calls by PAM into modules. + You can use nonstick to implement a PAM module that performs any stage of the PAM lifecycle. -It provides fairly robust functionality for developing PAM modules (i.e., backends that PAM calls to authenticate users or do something similar). -[Linux-PAM](https://github.com/linux-pam/linux-pam) is the only _tested_ PAM implementation, but it compiles against OpenPAM. +- **Applications**: supports only a subset of PAM features: + + - Authentication + - Account management + - Password change + + The remaining features (credential and session management) are coming in a future release. + (It needs work on a safe and ergonomic API.) -*If youβre looking for a library to implement a PAM client* (i.e., something that authenticates using PAM), consider the [`pam` crate](https://crates.io/crates/pam) for now. +The overall shape of the API is largely complete and its general shape should not change significantly. +While there may still be minor source incompatibilities pre-1.0 (e.g., moving methods around), it is unlikely that the library will be radically reworked (and I will try to avoid them unless needed). + +## Testing + +Nonstick is tested against all supported PAM implementations. +In addition to doctests and unit tests in the source itself, the (non-public) `testharness` sub-package performs end-to-end tests against the whole authentication process. -APIs are likely to break before v0.1.0, but thereafter should stabilize to an eventual 1.0 release. -After v0.1.0, the shape of the API should be mostly formed, and most of what happens will be adding new features. +## Configuration + +By default, nonstick uses `libpam-sys` to detect which implementation of PAM it should build against. +You can also select to build your library or application against a specific PAM implementation by setting the `LIBPAMSYS_IMPL` environment variable. +See [the documentation for `libpam-sys`](https://docs.rs/libpam-sys/) for more details. + +## Cargo features -Goals include: +- `link` (enabled by default): Link against your system's PAM library. + If disabled, you can still use the PAM traits and enum types to build and test your own PAM code independent of your system PAM. +- `basic-ext` (enabled by default): Include enum values provided by both OpenPAM and Linux-PAM. +- `linux-pam-ext`: Include features specific to Linux-PAM, including enum values and the ability to send binary messages. +- `openpam-ext`: Include features specific to OpenPAM (just enum values). +- `sun-ext`: Include features specific to Sun PAM (just enum values). -- Bindings for PAM clients. -- Additional PAM features, like environment variables. -- Way more documentation. +When `link` is enabled, you can only use the PAM features available on the configured PAM implementation. +For instance, when building with Linux-PAM, `link` and `openpam-ext` cannot be used together. + +However, when `link` is disabled, you could develop and test a crate with `sun-ext` enabled using any device. ## Credits
--- a/libpam-sys/libpam-sys-helpers/src/lib.rs Tue Jul 29 16:52:32 2025 -0400 +++ b/libpam-sys/libpam-sys-helpers/src/lib.rs Tue Jul 29 18:58:27 2025 -0400 @@ -55,7 +55,6 @@ /// β data ββββββββ«ββ> ... /// βββββββββββββββ’ /// β ... β -/// /// ``` #[derive(Debug)] pub struct PtrPtrVec<T> {
--- a/libpam-sys/src/aliases.rs Tue Jul 29 16:52:32 2025 -0400 +++ b/libpam-sys/src/aliases.rs Tue Jul 29 18:58:27 2025 -0400 @@ -10,13 +10,16 @@ /// (and a workaround). /// /// ```no_run +/// use libpam_sys::aliases::ConversationCallback; /// use libpam_sys::pam_conv; -/// use libpam_sys::aliases::ConversationCallback; /// fn convo() -> ConversationCallback { /// // ... /// # unimplemented!() /// } -/// let conv = pam_conv{conv: convo(), appdata_ptr: std::ptr::null_mut()}; +/// let conv = pam_conv { +/// conv: convo(), +/// appdata_ptr: std::ptr::null_mut(), +/// }; /// ``` pub type ConversationCallback = unsafe extern "C" fn( num_msg: c_int, @@ -29,8 +32,8 @@ /// /// ```no_run /// # use std::ffi::CString; +/// use libpam_sys::aliases::CleanupCallback; /// use libpam_sys::pam_set_data; -/// use libpam_sys::aliases::CleanupCallback; /// # use libpam_sys::pam_handle; /// # let handle: *mut pam_handle = std::ptr::null_mut(); /// # let mut my_data = 100;
--- a/libpam-sys/src/lib.rs Tue Jul 29 16:52:32 2025 -0400 +++ b/libpam-sys/src/lib.rs Tue Jul 29 18:58:27 2025 -0400 @@ -8,7 +8,6 @@ //! - The [`aliases`] submodule, which contains convenient aliases //! for callback types used in libpam, so you don't have to type //! `unsafe extern "C" fn(this is so long)` all the time. -//! #![doc = concat!("This documentation was built for the **", pam_impl_name!(), "** implementation.")] //! //! You can override this **at build time** by setting the `LIBPAMSYS_IMPL` @@ -411,13 +410,11 @@ /// # let service = CString::new("whatever").unwrap(); /// # let user = CString::new("whatever").unwrap(); /// let mut handle: *mut pam_handle = ptr::null_mut(); - /// let mut conv = pam_conv{ + /// let mut conv = pam_conv { /// conv: openpam_ttyconv, /// appdata_ptr: ptr::null_mut(), /// }; - /// let result = unsafe { pam_start( - /// service.as_ptr(), user.as_ptr(), &mut conv, &mut handle - /// ) }; + /// let result = unsafe { pam_start(service.as_ptr(), user.as_ptr(), &mut conv, &mut handle) }; /// ``` pub fn openpam_ttyconv( n: c_int, @@ -437,13 +434,11 @@ /// # let service = CString::new("whatever").unwrap(); /// # let user = CString::new("whatever").unwrap(); /// let mut handle: *mut pam_handle = ptr::null_mut(); - /// let mut conv = pam_conv{ + /// let mut conv = pam_conv { /// conv: openpam_nullconv, /// appdata_ptr: ptr::null_mut(), /// }; - /// let result = unsafe { pam_start( - /// service.as_ptr(), user.as_ptr(), &mut conv, &mut handle - /// ) }; + /// let result = unsafe { pam_start(service.as_ptr(), user.as_ptr(), &mut conv, &mut handle) }; /// ``` pub fn openpam_nullconv( n: c_int,
--- a/src/_doc.rs Tue Jul 29 16:52:32 2025 -0400 +++ b/src/_doc.rs Tue Jul 29 18:58:27 2025 -0400 @@ -143,7 +143,7 @@ /// /// ```ignore /// # use nonstick::_doc::mansun; -/// // Both of these formulations create a link named `manbsd`. +/// // Both of these formulations create a link named `mansun`. /// #[doc = mansun!(3pam fn_name)] /// #[doc = mansun!(5 "a.out" "synopsis")] /// // This one creates a link named `link_name`. @@ -154,12 +154,18 @@ ($n:literal $func:ident $($anchor:literal)?) => { $crate::_doc::mansun!(mansun: [$n ""] $func $($anchor)?) }; + ($n:literal $func:literal $($anchor:literal)?) => { + $crate::_doc::mansun!(mansun: [$n ""] $func $($anchor)?) + }; ([$n:literal $sect:literal] $func:ident $($anchor:literal)?) => { $crate::_doc::mansun!(mansun: [$n $sect] $func $($anchor)?) }; ($name:ident: $n:literal $func:ident $($anchor:literal)?) => { $crate::_doc::mansun!($name: [$n ""] $func $($anchor)?) }; + ($name:ident: $n:literal $func:literal $($anchor:literal)?) => { + $crate::_doc::mansun!($name: [$n ""] ($func) $($anchor)?) + }; ($name:ident: [$n:literal $sect:literal] $func:ident $($anchor:literal)?) => { $crate::_doc::mansun!($name: [$n $sect] (stringify!($func)) $($anchor)?) };
--- a/src/handle.rs Tue Jul 29 16:52:32 2025 -0400 +++ b/src/handle.rs Tue Jul 29 18:58:27 2025 -0400 @@ -50,7 +50,6 @@ /// 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)] @@ -61,7 +60,6 @@ /// 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)] @@ -76,7 +74,6 @@ /// 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")] @@ -87,7 +84,6 @@ /// 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")] @@ -165,7 +161,6 @@ /// retrieves the existing item. /// /// # References - /// #[doc = linklist!(pam_get_authtok: man7, manbsd)] /// /// # Example @@ -190,7 +185,6 @@ /// This should only be used by a *password-change* module. /// /// # References - /// #[doc = linklist!(pam_get_authtok: man7, manbsd)] /// /// # Example @@ -205,7 +199,6 @@ /// Ok(()) /// # } /// ``` - /// #[doc = stdlinks!(3 pam_get_authtok)] fn old_authtok(&mut self, prompt: Option<&OsStr>) -> Result<OsString>; @@ -229,7 +222,6 @@ /// ``` /// /// # References - /// #[doc = linklist!(pam_get_data: mwg, _std)] /// #[doc = guide!(mwg: "mwg-expected-by-module-item.html#mwg-pam_get_data")] @@ -282,8 +274,8 @@ /// match client.get_module_data::<u64>("TOKEN_ID") { /// Some(&tid) => { /// // This will execute and tid will be 0x0fa1afe10000beef. - /// }, - /// None => { /* This will not execute. */ }, + /// } + /// None => { /* This will not execute. */ } /// } /// Ok(()) /// } @@ -305,10 +297,10 @@ /// 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(()) @@ -349,7 +341,6 @@ /// 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")]
--- a/src/items.rs Tue Jul 29 16:52:32 2025 -0400 +++ b/src/items.rs Tue Jul 29 18:58:27 2025 -0400 @@ -40,7 +40,6 @@ /// 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")] @@ -104,7 +103,6 @@ /// 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")]
--- a/src/lib.rs Tue Jul 29 16:52:32 2025 -0400 +++ b/src/lib.rs Tue Jul 29 18:58:27 2025 -0400 @@ -1,26 +1,171 @@ -//! A safe, nonstick interface to PAM. +//! A safe, nonstick interface to the Pluggable Authentication Module framework. +//! +//! Nonstick provides a fully type- and memory-safe interface to +//! all implementations of PAM, both for PAM modules and PAM applications. +//! +//! # Usage +//! +//! nonstick can be used on either side of a PAM transaction, +//! both to implement an application which calls into PAM, +//! or a module which implements a PAM backend. +//! +//! For more information about how PAM works in general, or more pointers +//! on how to implement a PAM module or application, see the +//! [References](#references) section. +//! +//! ## PAM Application +//! +//! To implement a PAM application, first implement a [`Conversation`], +//! then build a [`Transaction`] with the [`TransactionBuilder`]. +//! This can be built into any standard Rust library or binary. +//! +//! ``` +//! use nonstick::{ +//! AuthnFlags, Conversation, ConversationAdapter, Result as PamResult, Transaction, +//! TransactionBuilder, +//! }; +//! use std::ffi::{OsStr, OsString}; +//! +//! /// A basic Conversation that assumes that any "regular" prompt is for +//! /// the username, and that any "masked" prompt is for the password. +//! /// +//! /// A typical Conversation will provide the user with an interface +//! /// to interact with PAM, e.g. a dialogue box or a terminal prompt. +//! struct UsernamePassConvo { +//! username: String, +//! password: String, +//! } //! -//! This implements a type-safe library to interact with PAM. -//! Currently, it implements a subset of PAM useful for implementing a module. +//! // ConversationAdapter is a convenience wrapper for the common case +//! // of only handling one request at a time. +//! impl ConversationAdapter for UsernamePassConvo { +//! fn prompt(&self, request: impl AsRef<OsStr>) -> PamResult<OsString> { +//! Ok(OsString::from(&self.username)) +//! } +//! +//! fn masked_prompt(&self, request: impl AsRef<OsStr>) -> PamResult<OsString> { +//! Ok(OsString::from(&self.password)) +//! } +//! +//! fn error_msg(&self, message: impl AsRef<OsStr>) { +//! // Normally you would want to display this to the user somehow. +//! // In this case, we're just ignoring it. +//! } +//! +//! fn info_msg(&self, message: impl AsRef<OsStr>) { +//! // ibid. +//! } +//! } //! -//! To write a new PAM module using this crate: +//! fn authenticate(username: &str, password: &str) -> PamResult<()> { +//! let user_pass = UsernamePassConvo { +//! username: username.into(), +//! password: password.into(), +//! }; +//! +//! let mut txn = TransactionBuilder::new_with_service("cortex-sso") +//! .username(username) +//! .build(user_pass.into_conversation())?; +//! // If authentication fails, this will return an error. +//! // We immediately give up rather than re-prompting the user. +//! txn.authenticate(AuthnFlags::empty())?; +//! txn.account_management(AuthnFlags::empty())?; +//! Ok(()) +//! } +//! ``` +//! +//! PAM just tells you that the user is, in fact, who they say they are. +//! It is up to your application to choose what to do with that information. +//! +//! ## PAM module //! -//! 1. Create a `dylib` crate. -//! 2. Implement a subset of the functions in the [`PamModule`] trait -//! corresponding to what you want your module to do. -//! In the simplest case (for a new password-based authenticator), -//! this will be the [`PamModule::authenticate`] function. -//! 3. Export your PAM module using the [`pam_export!`] macro. -//! 4. Build and install the dynamic library. -//! This usually entails placing it at -//! <code>/usr/lib/security/pam_<var>your_module</var>.so</code>, -//! or maybe -//! <code>/usr/lib/<var>your-architecture</var>/security/pam_<var>your_module</var>.so</code>. +//! PAM modules are implemented as dynamic libraries loaded into +//! the address space of the calling application. To implement a module, +//! create a `dylib` crate and implement a [`PamModule`], and export it +//! using the [`pam_export!`] macro. +//! +//! ```toml +//! ## Your Cargo.toml +//! [package] +//! name = "example-package" +//! ## ... +//! +//! [lib] +//! crate-type = ["cdylib"] +//! ``` +//! +//! ``` +//! // Your lib.rs +//! +//! use nonstick::{ +//! pam_export, AuthnFlags, ErrorCode, ModuleClient, PamModule, Result as PamResult, +//! }; +//! use std::ffi::CStr; +//! +//! # // This needs to be here to make this doc example work. +//! # fn main() {} +//! +//! /// A module that only allows you to log in if your username +//! /// is the same as your password. +//! struct SameName; +//! pam_export!(SameName); +//! +//! impl<M: ModuleClient> PamModule<M> for SameName { +//! fn authenticate(handle: &mut M, _args: Vec<&CStr>, _flags: AuthnFlags) -> PamResult<()> { +//! // Using `None` as the prompt parameter here will tell PAM +//! // to use the default prompt. +//! let username = handle.username(None)?; +//! let password = handle.authtok(None)?; +//! if username == password { +//! Ok(()) +//! } else { +//! Err(ErrorCode::AuthenticationError) +//! } +//! } //! -//! For general information on writing PAM modules, see -//! [The Linux-PAM Module Writers' Guide][module-guide] +//! // You can implement other methods of PamModule to provide additional +//! // features. +//! } +//! ``` +//! +//! This gets built into a library like `pam_samename.so`. By installing this +//! into your PAM library directory and configuring PAM to use it in +//! the authentication stack (beyond the scope of this documentation), it will +//! be used to authenticate users. +//! +//! # Configuration +//! +//! There are a few different PAM implementations available. By default, +//! nonstick detects which implementation it should use for the current target. +//! If you need to choose a different implementation, set the `LIBPAMSYS_IMPL` +//! environment variable at build time. See the [`libpam_sys`] documentation. +#![doc = concat!("This documentation was built for **", pam_impl_name!(), "**.")] +//! +//! # References //! -//! [module-guide]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/Linux-PAM_MWG.html +//! - The Linux-PAM guides provide information for a variety of audiences. +//! While some of it is specific to Linux-PAM, much of it applies to other +//! PAM implementations: +//! - [Application Developers' Guide][adg] +//! - [Module Writers' Guide][mwg] +//! - [System Administrators' Guide][sag] +//! - PAM framework man page for developers: +//! - [Linux-PAM developer man page][man7] +//! - [OpenPAM developer man page][manbsd] +//! - [Illumos PAM developer man page][mansun] +//! - PAM framework man page for system administrators: +//! - [Linux-PAM admin documentation][man7pam8] +//! - [OpenPAM admin documentation][bsdpam8] +//! - [Illumos pam.conf documentation][sunpam5] +//! - [The original PAM specification][spec] (mostly of historical interest) +#![doc = crate::_doc::man7!(man7pam8: 8 pam)] +#![doc = crate::_doc::manbsd!(bsdpam8: 8 pam)] +#![doc = crate::_doc::mansun!(sunpam5: 5 "pam.conf")] +#![doc = crate::_doc::stdlinks!(3 pam)] +#![doc = crate::_doc::guide!(adg: "Linux-PAM_ADG.html")] +#![doc = crate::_doc::guide!(mwg: "Linux-PAM_MWG.html")] +#![doc = crate::_doc::guide!(sag: "Linux-PAM_SAG.html")] +#![doc = crate::_doc::xsso!(spec: "toc.htm")] #[cfg(feature = "link")] mod _compat_checker { @@ -73,3 +218,4 @@ handle::{ModuleClient, PamShared, Transaction}, module::PamModule, }; +use libpam_sys::pam_impl_name;
--- a/src/libpam/handle.rs Tue Jul 29 16:52:32 2025 -0400 +++ b/src/libpam/handle.rs Tue Jul 29 18:58:27 2025 -0400 @@ -44,6 +44,9 @@ } } +/// Builder to start a [`LibPamTransaction`]. +/// +/// Use [`Self::new_with_service`] to build a new PAM transaction. #[derive(Debug, PartialEq)] pub struct TransactionBuilder { service_name: OsString, @@ -57,6 +60,9 @@ /// when authenticating a user. This corresponds to the configuration file /// usually at <code>/etc/pam.d/<var>service_name</var></code>. /// + /// You usually want to call [`username`](Self::username) to set + /// the username before starting the transaction. + /// /// # References #[doc = linklist!(pam_start: adg, _std)] /// @@ -132,7 +138,6 @@ /// /// On other platforms, this is no different than letting the transaction /// end on its own. - /// #[doc = man7!(3 pam_end)] pub fn end_silent(self) { #[cfg(pam_impl = "LinuxPam")]
--- a/src/libpam/module.rs Tue Jul 29 16:52:32 2025 -0400 +++ b/src/libpam/module.rs Tue Jul 29 18:58:27 2025 -0400 @@ -17,7 +17,7 @@ /// /// ```no_run /// use nonstick::{ -/// pam_export, ConversationAdapter, AuthnFlags, LibPamTransaction, ModuleClient, PamModule, +/// pam_export, AuthnFlags, ConversationAdapter, LibPamTransaction, ModuleClient, PamModule, /// Result as PamResult, /// }; /// use std::ffi::CStr; @@ -35,7 +35,11 @@ /// Ok(()) /// } /// -/// fn account_management(handle: &mut T, args: Vec<&CStr>, flags: AuthnFlags) -> PamResult<()> { +/// fn account_management( +/// handle: &mut T, +/// args: Vec<&CStr>, +/// flags: AuthnFlags, +/// ) -> PamResult<()> { /// let username = handle.username(None)?; /// let response = format!("Hello {username:?}! I trust you unconditionally."); /// handle.info_msg(&response);
--- a/src/logging.rs Tue Jul 29 16:52:32 2025 -0400 +++ b/src/logging.rs Tue Jul 29 18:58:27 2025 -0400 @@ -34,8 +34,8 @@ /// /// ```no_run /// # use nonstick::PamShared; + /// use nonstick::location; /// use nonstick::logging::Level; - /// use nonstick::location; /// # fn _test(pam_hdl: impl PamShared) { /// # let delay_ms = 100; /// # let url = "https://zombo.com"; @@ -45,7 +45,11 @@ /// 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::Warn, location!(), format_args!("this is unnecessarily verbose")); + /// pam_hdl.log( + /// Level::Warn, + /// location!(), + /// format_args!("this is unnecessarily verbose"), + /// ); /// # } /// ``` #[doc = man7!(3 pam_syslog)]