# HG changeset patch # User Paul Fisher # Date 1747893152 14400 # Node ID bbe84835d6db43d15b831fbec1c914139c3d8eaf # Parent a7aa5ca0d00d09750bcd10a0f25e59ae62b27f21 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. diff -r a7aa5ca0d00d -r bbe84835d6db src/constants.rs --- 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, diff -r a7aa5ca0d00d -r bbe84835d6db src/conv.rs --- 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, diff -r a7aa5ca0d00d -r bbe84835d6db src/handle.rs --- /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::()`. + /// 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> { + /// // 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 { + 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::()`. + /// + /// 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> { + /// // 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 { + 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> { + /// let svc: Option = 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(&self) -> crate::Result> { + 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(&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(&self, key: &str) -> crate::Result> { + 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(&mut self, key: &str, data: Box) -> 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::, + ) + }; + 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(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) { + unsafe { + let _data: Box = 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) + } +} diff -r a7aa5ca0d00d -r bbe84835d6db src/lib.rs --- 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, }; diff -r a7aa5ca0d00d -r bbe84835d6db src/memory.rs --- 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> { +pub fn option_cstr(prompt: Option<&str>) -> Result> { prompt .map(CString::new) .transpose() @@ -22,7 +22,7 @@ /// Creates an owned copy of a string that is returned from a /// pam_get_whatever function. -pub fn copy_pam_string(result_ptr: *const c_char) -> crate::Result { +pub fn copy_pam_string(result_ptr: *const c_char) -> Result { // 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()); diff -r a7aa5ca0d00d -r bbe84835d6db src/module.rs --- 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(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) { - unsafe { - let _data: Box = 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(&self, key: &str) -> Result> { - 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(&mut self, key: &str, data: Box) -> 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::, - ) - }; - 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(&self) -> Result> { - 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(&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 { - 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 { - 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. diff -r a7aa5ca0d00d -r bbe84835d6db src/pam_ffi.rs --- 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;