diff src/lib.rs @ 174:9e4ce1631bd3

Dramatically expand documentation.
author Paul Fisher <paul@pfish.zone>
date Tue, 29 Jul 2025 18:58:27 -0400
parents 46e8ce5cd5d1
children e30775c80b49
line wrap: on
line diff
--- 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;