changeset 64:bbe84835d6db v0.0.5

More organization; add lots of docs. - moves `PamHandle` to its own module, since it will be used by both modules and clients. - adds a ton of documentation to the `PamModule` trait and reorders methods to most-interesting-first. - adds more flag values from pam_modules.h. - other misc cleanup.
author Paul Fisher <paul@pfish.zone>
date Thu, 22 May 2025 01:52:32 -0400
parents a7aa5ca0d00d
children 8e507c7af9cf
files src/constants.rs src/conv.rs src/handle.rs src/lib.rs src/memory.rs src/module.rs src/pam_ffi.rs
diffstat 7 files changed, 482 insertions(+), 216 deletions(-) [+]
line wrap: on
line diff
--- a/src/constants.rs	Wed May 21 23:19:43 2025 -0400
+++ b/src/constants.rs	Thu May 22 01:52:32 2025 -0400
@@ -10,28 +10,56 @@
 bitflags! {
     /// The available PAM flags.
     ///
-    /// See `/usr/include/security/_pam_types.h` for more details.
+    /// See `/usr/include/security/_pam_types.h` and
+    /// See `/usr/include/security/pam_modules.h` for more details.
     #[derive(Debug, PartialEq)]
     #[repr(transparent)]
     pub struct Flags: c_uint {
-        /// Authentication service should not generate any messages.
+        /// The module should not generate any messages.
         const SILENT = 0x8000;
-        /// The service should return [ErrorCode::AuthError] if the user
-        /// has a null authentication token.
+
+        /// The module should return [ErrorCode::AuthError]
+        /// if the user has an empty authentication token
+        /// rather than immediately accepting them.
         const DISALLOW_NULL_AUTHTOK = 0x0001;
+
+        // Flag used for `set_credentials`.
+
         /// Set user credentials for an authentication service.
-        const ESTABLISH_CRED = 0x0002;
+        const ESTABLISH_CREDENTIALS = 0x0002;
         /// Delete user credentials associated with
         /// an authentication service.
-        const DELETE_CRED = 0x0004;
+        const DELETE_CREDENTIALS = 0x0004;
         /// Reinitialize user credentials.
-        const REINITIALIZE_CRED = 0x0008;
+        const REINITIALIZE_CREDENTIALS = 0x0008;
         /// Extend the lifetime of user credentials.
-        const REFRESH_CRED = 0x0010;
+        const REFRESH_CREDENTIALS = 0x0010;
+
+        // Flags used for password changing.
+
         /// The password service should only update those passwords
         /// that have aged. If this flag is _not_ passed,
         /// the password service should update all passwords.
+        ///
+        /// This flag is only used by `change_authtok`.
         const CHANGE_EXPIRED_AUTHTOK = 0x0020;
+
+        /// This is a preliminary check for password changing.
+        /// The password should not be changed.
+        ///
+        /// This is only used between PAM and a module.
+        /// Applications may not use this flag.
+        ///
+        /// This flag is only used by `change_authtok`.
+        const PRELIMINARY_CHECK = 0x4000;
+        /// The password should actuallyPR be updated.
+        /// This and [Self::PRELIMINARY_CHECK] are mutually exclusive.
+        ///
+        /// This is only used between PAM and a module.
+        /// Applications may not use this flag.
+        ///
+        /// This flag is only used by `change_authtok`.
+        const UPDATE_AUTHTOK = 0x2000;
     }
 }
 
@@ -90,7 +118,7 @@
     AuthTokLockBusy = 22,
     #[error("authentication token aging disabled")]
     AuthTokDisableAging = 23,
-    #[error("preliminary check by password service")]
+    #[error("preliminary password check failed")]
     TryAgain = 24,
     #[error("ignore underlying account module, regardless of control flag")]
     Ignore = 25,
--- a/src/conv.rs	Wed May 21 23:19:43 2025 -0400
+++ b/src/conv.rs	Thu May 22 01:52:32 2025 -0400
@@ -3,14 +3,14 @@
 //! This module is experimental and will probably be rewritten in the future
 //! to improve the interface for both PAM modules and clients.
 
+use crate::constants::MessageStyle;
+use crate::constants::Result;
+use crate::constants::{ErrorCode, InvalidEnum};
+use crate::items::Item;
 use libc::{c_char, c_int};
+use num_derive::FromPrimitive;
 use std::ffi::{CStr, CString};
 use std::ptr;
-use num_derive::FromPrimitive;
-use crate::constants::{ErrorCode, InvalidEnum};
-use crate::constants::MessageStyle;
-use crate::constants::Result;
-use crate::items::Item;
 
 /// Styles of message that are shown to the user.
 #[derive(Debug, PartialEq, FromPrimitive)]
@@ -44,7 +44,6 @@
     }
 }
 
-
 #[repr(C)]
 struct Message {
     msg_style: MessageStyle,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/handle.rs	Thu May 22 01:52:32 2025 -0400
@@ -0,0 +1,208 @@
+//! Where [PamHandle] lives.
+use crate::items::{Item, ItemType};
+use crate::{memory, pam_ffi, ErrorCode};
+use libc::c_char;
+use secure_string::SecureString;
+use std::ffi::{c_int, CString};
+
+/// Your interface to a PAM handle.
+///
+/// This structure wraps an opaque PAM-provided pointer and gives you
+/// a safe and familiar struct-based API to interact with PAM.
+#[repr(transparent)]
+pub struct PamHandle(*mut libc::c_void);
+
+impl PamHandle {
+    /// Retrieves the name of the user who is authenticating or logging in.
+    ///
+    /// This is effectively like `handle.get_item::<Item::User>()`.
+    /// See the [`pam_get_user` manual page][man]
+    /// or [`pam_get_user` in the Module Writer's Guide][mwg].
+    ///
+    /// # Example
+    ///
+    /// ```no_run
+    /// # use nonstick::PamHandle;
+    /// # fn _doc(handle: &PamHandle) -> 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.
+    /// let user = 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
+    pub fn get_user(&self, prompt: Option<&str>) -> crate::Result<String> {
+        let prompt = memory::option_cstr(prompt)?;
+        let mut output: *const c_char = std::ptr::null_mut();
+        let ret = unsafe {
+            pam_ffi::pam_get_user(self.0, &mut output, memory::prompt_ptr(prompt.as_ref()))
+        };
+        ErrorCode::result_from(ret)?;
+        memory::copy_pam_string(output)
+    }
+
+    /// Retrieves the authentication token from the user.
+    ///
+    /// This is essentially like `handle.get_item::<Item::AuthTok>()`.
+    ///
+    /// See the [`pam_get_authtok` manual page][man]
+    /// or [`pam_get_item` in the Module Writer's Guide][mwg].
+    ///
+    /// # Example
+    ///
+    /// ```no_run
+    /// # use nonstick::PamHandle;
+    /// # fn _doc(handle: &PamHandle) -> 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
+    pub fn get_authtok(&self, prompt: Option<&str>) -> crate::Result<SecureString> {
+        let prompt = memory::option_cstr(prompt)?;
+        let mut output: *const c_char = std::ptr::null_mut();
+        let res = unsafe {
+            pam_ffi::pam_get_authtok(
+                self.0,
+                ItemType::AuthTok.into(),
+                &mut output,
+                memory::prompt_ptr(prompt.as_ref()),
+            )
+        };
+        ErrorCode::result_from(res)?;
+        memory::copy_pam_string(output).map(SecureString::from)
+    }
+
+    /// Retrieves an [Item] that has been set, possibly by the PAM client.
+    ///
+    /// These items are *references to PAM memory*
+    /// which are *owned by the PAM session*
+    /// and you should never modify them.
+    ///
+    /// See the [`pam_get_item` manual page][man]
+    /// or [`pam_get_item` in the Module Writer's Guide][mwg].
+    ///
+    /// # Example
+    ///
+    /// ```no_run
+    /// # use nonstick::PamHandle;
+    /// use nonstick::items::Service;
+    ///
+    /// # fn _doc(pam_handle: &PamHandle) -> Result<(), Box<dyn std::error::Error>> {
+    /// let svc: Option<Service> = pam_handle.get_item()?;
+    /// match svc {
+    ///     Some(name) => eprintln!("The calling service name is {:?}", name.to_string_lossy()),
+    ///     None => eprintln!("Who knows what the calling service is?"),
+    /// }
+    /// # Ok(())
+    /// # }
+    /// ```
+    ///
+    /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html
+    /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item
+    pub fn get_item<T: Item>(&self) -> crate::Result<Option<T>> {
+        let mut ptr: *const libc::c_void = std::ptr::null();
+        let out = unsafe {
+            let ret = pam_ffi::pam_get_item(self.0, T::type_id().into(), &mut ptr);
+            ErrorCode::result_from(ret)?;
+            let typed_ptr: *const T::Raw = ptr.cast();
+            match typed_ptr.is_null() {
+                true => None,
+                false => Some(T::from_raw(typed_ptr)),
+            }
+        };
+        Ok(out)
+    }
+
+    /// Sets an item in the PAM context. It can be retrieved using [`get_item`](Self::get_item).
+    ///
+    /// See the [`pam_set_item` manual page][man]
+    /// or [`pam_set_item` in the Module Writer's Guide][mwg].
+    ///
+    /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html
+    /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_item
+    pub fn set_item<T: Item>(&mut self, item: T) -> crate::Result<()> {
+        let ret =
+            unsafe { pam_ffi::pam_set_item(self.0, T::type_id().into(), item.into_raw().cast()) };
+        ErrorCode::result_from(ret)
+    }
+
+    /// 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
+    pub unsafe fn get_data<T>(&self, key: &str) -> crate::Result<Option<&T>> {
+        let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?;
+        let mut ptr: *const libc::c_void = std::ptr::null();
+        ErrorCode::result_from(pam_ffi::pam_get_data(self.0, c_key.as_ptr(), &mut ptr))?;
+        match ptr.is_null() {
+            true => Ok(None),
+            false => {
+                let typed_ptr = ptr.cast();
+                Ok(Some(&*typed_ptr))
+            }
+        }
+    }
+
+    /// 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
+    pub fn set_data<T>(&mut self, key: &str, data: Box<T>) -> crate::Result<()> {
+        let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?;
+        let ret = unsafe {
+            pam_ffi::pam_set_data(
+                self.0,
+                c_key.as_ptr(),
+                Box::into_raw(data).cast(),
+                Self::set_data_cleanup::<T>,
+            )
+        };
+        ErrorCode::result_from(ret)
+    }
+
+    /// Function called at the end of a PAM session that is called to clean up
+    /// a value previously provided to PAM in a `pam_set_data` call.
+    ///
+    /// You should never call this yourself.
+    extern "C" fn set_data_cleanup<T>(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) {
+        unsafe {
+            let _data: Box<T> = Box::from_raw(c_data.cast());
+        }
+    }
+}
+
+impl From<*mut libc::c_void> for PamHandle {
+    /// Wraps an internal Handle pointer.
+    fn from(value: *mut libc::c_void) -> Self {
+        Self(value)
+    }
+}
--- a/src/lib.rs	Wed May 21 23:19:43 2025 -0400
+++ b/src/lib.rs	Thu May 22 01:52:32 2025 -0400
@@ -9,7 +9,7 @@
 //!  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 [`sm_authenticate`](PamModule::sm_authenticate) function.
+//!     this will be the [`PamModule::authenticate`] function.
 //!  3. Export your PAM module using the [`pam_hooks!`] macro.
 //!  4. Build and install the dynamic library.
 //!     This usually entails placing it at
@@ -26,13 +26,15 @@
 #[cfg(feature = "experimental")]
 pub mod conv;
 pub mod items;
-pub mod module;
+mod module;
 
+mod handle;
 mod memory;
 mod pam_ffi;
 
 #[doc(inline)]
 pub use crate::{
     constants::{ErrorCode, Flags, Result},
-    module::{PamHandle, PamModule},
+    handle::PamHandle,
+    module::PamModule,
 };
--- a/src/memory.rs	Wed May 21 23:19:43 2025 -0400
+++ b/src/memory.rs	Thu May 22 01:52:32 2025 -0400
@@ -1,11 +1,11 @@
 //! Utility functions for dealing with memory copying and stuff.
 
-use crate::ErrorCode;
+use crate::constants::{ErrorCode, Result};
 use libc::c_char;
 use std::ffi::{CStr, CString};
 
 /// Safely converts a `&str` option to a `CString` option.
-pub fn option_cstr(prompt: Option<&str>) -> crate::Result<Option<CString>> {
+pub fn option_cstr(prompt: Option<&str>) -> Result<Option<CString>> {
     prompt
         .map(CString::new)
         .transpose()
@@ -22,7 +22,7 @@
 
 /// Creates an owned copy of a string that is returned from a
 /// <code>pam_get_<var>whatever</var></code> function.
-pub fn copy_pam_string(result_ptr: *const c_char) -> crate::Result<String> {
+pub fn copy_pam_string(result_ptr: *const c_char) -> Result<String> {
     // We really shouldn't get a null pointer back here, but if we do, return nothing.
     if result_ptr.is_null() {
         return Ok(String::new());
--- a/src/module.rs	Wed May 21 23:19:43 2025 -0400
+++ b/src/module.rs	Thu May 22 01:52:32 2025 -0400
@@ -1,203 +1,234 @@
 //! Functions and types useful for implementing a PAM module.
 
 use crate::constants::{ErrorCode, Flags, Result};
-use crate::items::{Item, ItemType};
-use crate::memory;
-use libc::c_char;
-use secure_string::SecureString;
-use std::ffi::{c_int, CStr, CString};
+use crate::handle::PamHandle;
+use std::ffi::CStr;
 
-use crate::pam_ffi;
-
-/// Function called at the end of a PAM session that is called to clean up
-/// a value previously provided to PAM in a `pam_set_data` call.
+/// A trait for a PAM module to implement.
+///
+/// The default implementations of all these hooks tell PAM to ignore them
+/// (i.e., behave as if this module does not exist) by returning [`ErrorCode::Ignore`].
+/// Override any functions you wish to handle in your module.
+/// After implementing this trait, use the [`pam_hooks!`](crate::pam_hooks!) macro
+/// to make the functions available to PAM.
 ///
-/// You should never call this yourself.
-extern "C" fn cleanup<T>(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) {
-    unsafe {
-        let _data: Box<T> = Box::from_raw(c_data.cast());
-    }
-}
+/// For more information, see [`pam(3)`’s root manual page][manpage]
+/// and the [PAM Module Writer’s Guide][mwg].
+///
+/// [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 {
+    // Functions for auth modules.
 
-/// An opaque structure pointing to a PAM handle.
-#[repr(transparent)]
-pub struct PamHandle(*mut libc::c_void);
-
-impl PamHandle {
-    /// Gets some value, identified by `key`, that has been set by the module
-    /// previously.
+    /// Authenticate the user.
     ///
-    /// See the [`pam_get_data` manual page](
-    /// https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html).
-    ///
-    /// # Safety
+    /// This is probably the first thing you want to implement.
+    /// In most cases, you will want to get the user and password,
+    /// using [`PamHandle::get_user`] and [`PamHandle::get_authtok`],
+    /// and verify them against something.
     ///
-    /// The data stored under the provided key must be of type `T` otherwise the
-    /// behaviour of this function is undefined.
+    /// See [the Module Writer's Guide entry for `pam_sm_authenticate`][mwg]
+    /// for more information.
+    ///
+    /// # Valid flags
+    ///
+    /// This function may be called with the following flags set:
+    ///
+    /// - [`Flags::SILENT`]
+    /// - [`Flags::DISALLOW_NULL_AUTHTOK`]
+    ///
+    /// # Returns
+    ///
+    /// If the password check was successful, return `Ok(())`.
     ///
-    /// The data, if present, is owned by the current PAM conversation.
-    pub unsafe fn get_data<T>(&self, key: &str) -> Result<Option<&T>> {
-        let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?;
-        let mut ptr: *const libc::c_void = std::ptr::null();
-        ErrorCode::result_from(pam_ffi::pam_get_data(self.0, c_key.as_ptr(), &mut ptr))?;
-        match ptr.is_null() {
-            true => Ok(None),
-            false => {
-                let typed_ptr = ptr.cast();
-                Ok(Some(&*typed_ptr))
-            }
-        }
-    }
-
-    /// Stores a value that can be retrieved later with `get_data`.
-    /// The conversation takes ownership of the data.
+    /// Sensible error codes to return include:
     ///
-    /// See the [`pam_set_data` manual page](
-    /// https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html).
-    pub fn set_data<T>(&mut self, key: &str, data: Box<T>) -> Result<()> {
-        let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?;
-        let ret = unsafe {
-            pam_ffi::pam_set_data(
-                self.0,
-                c_key.as_ptr(),
-                Box::into_raw(data).cast(),
-                cleanup::<T>,
-            )
-        };
-        ErrorCode::result_from(ret)
+    /// - [`ErrorCode::AuthenticationError`]: Generic authentication error
+    ///   (like an incorrect password).
+    /// - [`ErrorCode::CredentialsInsufficient`]: The application does not have
+    ///   sufficient credentials to authenticate the user.
+    /// - [`ErrorCode::AuthInfoUnavailable`]: The module was not able to access
+    ///   the authentication information, for instance due to a network failure.
+    /// - [`ErrorCode::UserUnknown`]: The supplied username is not known by this service.
+    /// - [`ErrorCode::MaxTries`]: The user has tried authenticating too many times.
+    ///   They should not try again.
+    ///
+    /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-of-module-auth.html#mwg-pam_sm_authenticate
+    fn authenticate(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
+        Err(ErrorCode::Ignore)
     }
 
-    /// Retrieves a value that has been set, possibly by the pam client.
-    /// This is particularly useful for getting a `PamConv` reference.
+    /// Perform "account management".
     ///
-    /// These items are *references to PAM memory*
-    /// which are *owned by the conversation*.
+    /// When PAM calls this function, the user has already been authenticated
+    /// by an authentication module (either this one or some other module).
+    /// This hook can check for other things, for instance:
+    ///
+    /// - Date/time (keep your kids off the computer at night)
+    /// - Remote host (only let employees log in from the office)
     ///
-    /// See the [`pam_get_item` manual page](
-    /// https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html).
-    pub fn get_item<T: crate::items::Item>(&self) -> Result<Option<T>> {
-        let mut ptr: *const libc::c_void = std::ptr::null();
-        let out = unsafe {
-            let ret = pam_ffi::pam_get_item(self.0, T::type_id().into(), &mut ptr);
-            ErrorCode::result_from(ret)?;
-            let typed_ptr: *const T::Raw = ptr.cast();
-            match typed_ptr.is_null() {
-                true => None,
-                false => Some(T::from_raw(typed_ptr)),
-            }
-        };
-        Ok(out)
-    }
-
-    /// Sets an item in the pam context. It can be retrieved using `get_item`.
+    /// You can also check things like, e.g., password expiration,
+    /// and alert that the user change it before continuing,
+    /// or really do whatever you want.
     ///
-    /// See the [`pam_set_item` manual page](
-    /// https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html).
-    pub fn set_item<T: Item>(&mut self, item: T) -> Result<()> {
-        let ret =
-            unsafe { pam_ffi::pam_set_item(self.0, T::type_id().into(), item.into_raw().cast()) };
-        ErrorCode::result_from(ret)
-    }
-
-    /// Retrieves the name of the user who is authenticating or logging in.
+    /// See [the Module Writer's Guide entry for `pam_sm_acct_mgmt`][mwg]
+    /// for more information.
     ///
-    /// This is really a specialization of `get_item`.
+    ///
+    /// # Valid flags
     ///
-    /// See the [`pam_get_user` manual page](
-    /// https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html).
-    pub fn get_user(&self, prompt: Option<&str>) -> Result<String> {
-        let prompt = memory::option_cstr(prompt)?;
-        let mut output: *const c_char = std::ptr::null_mut();
-        let ret = unsafe {
-            pam_ffi::pam_get_user(self.0, &mut output, memory::prompt_ptr(prompt.as_ref()))
-        };
-        ErrorCode::result_from(ret)?;
-        memory::copy_pam_string(output)
-    }
-
-    /// Retrieves the authentication token from the user.
+    /// This function may be called with the following flags set:
+    ///
+    /// - [`Flags::SILENT`]
+    /// - [`Flags::DISALLOW_NULL_AUTHTOK`]
     ///
-    /// This is really a specialization of `get_item`.
+    /// # Returns
+    ///
+    /// If the user should be allowed to log in, return `Ok(())`.
+    ///
+    /// Sensible error codes to return include:
     ///
-    /// See the [`pam_get_authtok` manual page](
-    /// https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html).
-    pub fn get_authtok(&self, prompt: Option<&str>) -> Result<SecureString> {
-        let prompt = memory::option_cstr(prompt)?;
-        let mut output: *const c_char = std::ptr::null_mut();
-        let res = unsafe {
-            pam_ffi::pam_get_authtok(
-                self.0,
-                ItemType::AuthTok.into(),
-                &mut output,
-                memory::prompt_ptr(prompt.as_ref()),
-            )
-        };
-        ErrorCode::result_from(res)?;
-        memory::copy_pam_string(output).map(SecureString::from)
-    }
-}
-
-impl From<*mut libc::c_void> for PamHandle {
-    /// Wraps an internal Handle pointer.
-    fn from(value: *mut libc::c_void) -> Self {
-        Self(value)
-    }
-}
-
-/// Trait representing what a PAM module can do.
-///
-/// By default, all the functions in this trait are ignored.
-/// Implement any functions you wish to handle in your module.
-/// After implementing this trait, use the [crate::pam_hooks!] macro
-/// to export your functions.
-///
-/// For more information, see [`pam(3)`’s root manual page][manpage]
-/// and the [PAM Module Writer’s Guide][module-guide].
-///
-/// [manpage]: https://www.man7.org/linux/man-pages/man3/pam.3.html
-/// [module-guide]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/Linux-PAM_MWG.html
-#[allow(unused_variables)]
-pub trait PamModule {
-    /// This function performs the task of establishing whether the user is permitted to gain access at
-    /// this time. It should be understood that the user has previously been validated by an
-    /// authentication module. This function checks for other things. Such things might be: the time of
-    /// day or the date, the terminal line, remote hostname, etc. This function may also determine
-    /// things like the expiration on passwords, and respond that the user change it before continuing.
-    fn acct_mgmt(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
+    /// - [`ErrorCode::AccountExpired`]: The user's account has expired.
+    /// - [`ErrorCode::AuthenticationError`]: Generic authentication error.
+    /// - [`ErrorCode::NewAuthTokRequired`]: The user's authentication token has expired.
+    ///   PAM will ask the user to set a new authentication token, which may be handled by
+    ///   this module in [`Self::change_authtok`].
+    /// - [`ErrorCode::PermissionDenied`]: This one is pretty self-explanatory.
+    /// - [`ErrorCode::UserUnknown`]: The supplied username is not known by this service.
+    ///
+    /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-of-module-acct.html#mwg-pam_sm_acct_mgmt
+    fn account_management(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
         Err(ErrorCode::Ignore)
     }
 
-    /// This function performs the task of authenticating the user.
-    fn sm_authenticate(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
-        Err(ErrorCode::Ignore)
-    }
-
-    /// This function is used to (re-)set the authentication token of the user.
+    /// Set credentials on this session.
+    ///
+    /// If an authentication module knows more about the user than just
+    /// their authentication token, then it uses this function to provide
+    /// that information to the application. It should only be called after
+    /// authentication but before a session is established.
+    ///
+    /// See [the Module Writer's Guide entry for `pam_sm_setcred`][mwg]
+    /// for more information.
+    ///
+    /// # Valid flags
+    ///
+    /// This function may be called with the following flags set:
     ///
-    /// The PAM library calls this function twice in succession. The first time with
-    /// `PAM_PRELIM_CHECK` and then, if the module does not return `PAM_TRY_AGAIN`, subsequently with
-    /// `PAM_UPDATE_AUTHTOK`. It is only on the second call that the authorization token is
-    /// (possibly) changed.
-    fn sm_chauthtok(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
+    /// - [`Flags::SILENT`]
+    /// - [`Flags::ESTABLISH_CREDENTIALS`]: Initialize credentials for the user.
+    /// - [`Flags::DELETE_CREDENTIALS`]: Delete the credentials associated with this module.
+    /// - [`Flags::REINITIALIZE_CREDENTIALS`]: Re-initialize credentials for this user.
+    /// - [`Flags::REFRESH_CREDENTIALS`]: Extend the lifetime of the user's credentials.
+    ///
+    /// # Returns
+    ///
+    /// If credentials were set successfully, return `Ok(())`.
+    ///
+    /// Sensible error codes to return include:
+    ///
+    /// - [`ErrorCode::CredentialsUnavailable`]: The credentials cannot be retrieved.
+    /// - [`ErrorCode::CredentialsExpired`]: The credentials have expired.
+    /// - [`ErrorCode::CredentialsError`]: Some other error occurred when setting credentials.
+    /// - [`ErrorCode::UserUnknown`]: The supplied username is not known by this service.
+    ///
+    /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-of-module-auth.html#mwg-pam_sm_setcred
+    fn set_credentials(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
         Err(ErrorCode::Ignore)
     }
 
-    /// This function is called to terminate a session.
-    fn sm_close_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
+    // Function for chauthtok modules.
+
+    /// Called to set or reset the user's authentication token.
+    ///
+    /// PAM calls this function twice in succession.
+    ///  1. The first time, [`Flags::PRELIMINARY_CHECK`] will be set.
+    ///     If the new token is acceptable, return success;
+    ///     if not, return [`ErrorCode::TryAgain`] to re-prompt the user.
+    ///  2. After the preliminary check succeeds, [`Flags::UPDATE_AUTHTOK`]
+    ///     will be set. On this call, actually update the stored auth token.
+    ///
+    /// See [the Module Writer's Guide entry for `pam_sm_chauthtok`][mwg]
+    /// for more information.
+    ///
+    /// # Valid flags
+    ///
+    /// This function may be called with the following flags set:
+    ///
+    /// - [`Flags::SILENT`]
+    /// - [`Flags::CHANGE_EXPIRED_AUTHTOK`]: This module should only change
+    ///   any expired passwords, and leave non-expired passwords alone.
+    ///   If present, it _must_ be combined with one of the following.
+    /// - [`Flags::PRELIMINARY_CHECK`]: Don't actually change the password,
+    ///   just check if the new one is valid.
+    /// - [`Flags::UPDATE_AUTHTOK`]: Do actually change the password.
+    ///
+    /// # Returns
+    ///
+    /// If the authentication token was changed successfully
+    /// (or the check passed), return `Ok(())`.
+    ///
+    /// Sensible error codes to return include:
+    ///
+    /// - [`ErrorCode::AuthTokError`]: The service could not get the authentication token.
+    /// - [`ErrorCode::AuthTokRecoveryError`]: The service could not get the old token.
+    /// - [`ErrorCode::AuthTokLockBusy`]: The password cannot be changed because
+    ///   the authentication token is currently locked.
+    /// - [`ErrorCode::AuthTokDisableAging`]: Aging (expiration) is disabled.
+    /// - [`ErrorCode::PermissionDenied`]: What it says on the tin.
+    /// - [`ErrorCode::TryAgain`]: When the preliminary check is unsuccessful,
+    ///   ask the user for a new authentication token.
+    /// - [`ErrorCode::UserUnknown`]: The supplied username is not known by this service.
+    ///
+    /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-of-module-chauthtok.html#mwg-pam_sm_chauthtok
+    fn change_authtok(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
         Err(ErrorCode::Ignore)
     }
 
-    /// This function is called to commence a session.
-    fn sm_open_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
+    // Functions for session modules.
+
+    /// Called when a session is opened.
+    ///
+    /// See [the Module Writer's Guide entry for `pam_sm_open_session`][mwg]
+    /// for more information.
+    ///
+    /// # Valid flags
+    ///
+    /// The only valid flag is [`Flags::SILENT`].
+    ///
+    /// # Returns
+    ///
+    /// If the session was opened successfully, return `Ok(())`.
+    ///
+    /// A sensible error code to return is:
+    ///
+    /// - [`ErrorCode::SessionError`]: Cannot make an entry for this session.
+    ///
+    /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-of-module-session.html#mwg-pam_sm_open_session
+    fn open_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
         Err(ErrorCode::Ignore)
     }
 
-    /// This function performs the task of altering the credentials of the user with respect to the
-    /// corresponding authorization scheme. Generally, an authentication module may have access to more
-    /// information about a user than their authentication token. This function is used to make such
-    /// information available to the application. It should only be called after the user has been
-    /// authenticated but before a session has been established.
-    fn sm_setcred(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
+    /// Called when a session is being terminated.
+    ///
+    /// See [the Module Writer's Guide entry for `pam_sm_close_session`][mwg]
+    /// for more information.
+    ///
+    /// # Valid flags
+    ///
+    /// The only valid flag is [`Flags::SILENT`].
+    ///
+    /// # Returns
+    ///
+    /// If the session was closed successfully, return `Ok(())`.
+    ///
+    /// A sensible error code to return is:
+    ///
+    /// - [`ErrorCode::SessionError`]: Cannot remove an entry for this session.
+    ///
+    /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-of-module-session.html#mwg-pam_sm_close_session
+    fn close_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
         Err(ErrorCode::Ignore)
     }
 }
@@ -212,24 +243,26 @@
 ///
 /// Here is full example of a PAM module that would authenticate and authorize everybody:
 ///
-/// ```
+/// ```no_run
 /// use nonstick::{Flags, PamHandle, PamModule, Result as PamResult, pam_hooks};
 /// use std::ffi::CStr;
+/// # fn main() {}
 ///
-/// # fn main() {}
 /// struct MyPamModule;
 /// pam_hooks!(MyPamModule);
 ///
 /// impl PamModule for MyPamModule {
-///     fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
-///         // You should use a Conversation to communicate with the user
-///         // instead of writing to the console, but this is just an example.
-///         eprintln!("Everybody is authorized!");
+///     fn authenticate(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
+///         let password = handle.get_authtok(Some("what's your password?"))?;
+///         eprintln!("If you say your password is {:?}, who am I to disagree!", password.unsecure());
 ///         Ok(())
 ///     }
 ///
-///     fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
-///         eprintln!("Everybody is authenticated!");
+///     fn account_management(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
+///         let username = handle.get_user(None)?;
+///         // You should use a Conversation to communicate with the user
+///         // instead of writing to the console, but this is just an example.
+///         eprintln!("Hello {username}! I trust you unconditionally!");
 ///         Ok(())
 ///     }
 /// }
@@ -249,7 +282,11 @@
                 argv: *const *const c_char,
             ) -> c_int {
                 let args = extract_argv(argc, argv);
-                ErrorCode::result_to_c(super::$ident::acct_mgmt(&mut pamh.into(), args, flags))
+                ErrorCode::result_to_c(super::$ident::account_management(
+                    &mut pamh.into(),
+                    args,
+                    flags,
+                ))
             }
 
             #[no_mangle]
@@ -260,11 +297,7 @@
                 argv: *const *const c_char,
             ) -> c_int {
                 let args = extract_argv(argc, argv);
-                ErrorCode::result_to_c(super::$ident::sm_authenticate(
-                    &mut pamh.into(),
-                    args,
-                    flags,
-                ))
+                ErrorCode::result_to_c(super::$ident::authenticate(&mut pamh.into(), args, flags))
             }
 
             #[no_mangle]
@@ -275,7 +308,7 @@
                 argv: *const *const c_char,
             ) -> c_int {
                 let args = extract_argv(argc, argv);
-                ErrorCode::result_to_c(super::$ident::sm_chauthtok(&mut pamh.into(), args, flags))
+                ErrorCode::result_to_c(super::$ident::change_authtok(&mut pamh.into(), args, flags))
             }
 
             #[no_mangle]
@@ -286,11 +319,7 @@
                 argv: *const *const c_char,
             ) -> c_int {
                 let args = extract_argv(argc, argv);
-                ErrorCode::result_to_c(super::$ident::sm_close_session(
-                    &mut pamh.into(),
-                    args,
-                    flags,
-                ))
+                ErrorCode::result_to_c(super::$ident::close_session(&mut pamh.into(), args, flags))
             }
 
             #[no_mangle]
@@ -301,11 +330,7 @@
                 argv: *const *const c_char,
             ) -> c_int {
                 let args = extract_argv(argc, argv);
-                ErrorCode::result_to_c(super::$ident::sm_open_session(
-                    &mut pamh.into(),
-                    args,
-                    flags,
-                ))
+                ErrorCode::result_to_c(super::$ident::open_session(&mut pamh.into(), args, flags))
             }
 
             #[no_mangle]
@@ -316,7 +341,11 @@
                 argv: *const *const c_char,
             ) -> c_int {
                 let args = extract_argv(argc, argv);
-                ErrorCode::result_to_c(super::$ident::sm_setcred(&mut pamh.into(), args, flags))
+                ErrorCode::result_to_c(super::$ident::set_credentials(
+                    &mut pamh.into(),
+                    args,
+                    flags,
+                ))
             }
 
             /// Turns `argc`/`argv` into a [Vec] of [CStr]s.
--- a/src/pam_ffi.rs	Wed May 21 23:19:43 2025 -0400
+++ b/src/pam_ffi.rs	Thu May 22 01:52:32 2025 -0400
@@ -1,4 +1,4 @@
-//! Functions exported by the PAM FFI.
+//! FFI to the PAM library.
 
 use libc::c_char;
 use std::ffi::c_int;