Mercurial > crates > nonstick
changeset 166:2f5913131295
Separate flag/action flags into flags and action.
This also individualizes the type of flag for each PAM function,
so that you can only call a function with the right flags and values.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Tue, 15 Jul 2025 00:32:24 -0400 |
parents | c4b1e280463c |
children | 0cabe7b94a4f |
files | src/constants.rs src/handle.rs src/lib.rs src/libpam/handle.rs src/libpam/module.rs src/module.rs testharness/src/bin/testharness.rs testharness/src/lib.rs |
diffstat | 8 files changed, 310 insertions(+), 243 deletions(-) [+] |
line wrap: on
line diff
--- a/src/constants.rs Mon Jul 14 18:56:55 2025 -0400 +++ b/src/constants.rs Tue Jul 15 00:32:24 2025 -0400 @@ -2,6 +2,7 @@ use crate::_doc::{linklist, man7, manbsd, xsso}; use bitflags::bitflags; +use libpam_sys_consts::constants; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::error::Error; use std::ffi::c_int; @@ -9,99 +10,142 @@ use std::fmt::{Display, Formatter}; use std::result::Result as StdResult; -/// Values for constants not provided by certain PAM implementations. -/// -/// **The values of these constants are deliberately selected _not_ to match -/// any PAM implementations. Applications should always use the symbolic value -/// and not a magic number.** -mod pam_constants { - pub use libpam_sys_consts::constants::*; - - macro_rules! define { - ($(#[$attr:meta])* $($name:ident = $value:expr;)+) => { - define!( - @meta { $(#[$attr])* } - $(pub const $name: i32 = $value;)+ - ); - }; - (@meta $m:tt $($i:item)+) => { define!(@expand $($m $i)+); }; - (@expand $({ $(#[$m:meta])* } $i:item)+) => {$($(#[$m])* $i)+}; +/// Creates a bitflags! macro, with an extra SILENT element. +macro_rules! pam_flags { + ( + $(#[$m:meta])* + $name:ident { + $($inner:tt)* + } + ) => { + bitflags! { + $(#[$m])* + #[derive(Clone, Copy, Debug, Default, PartialEq)] + #[repr(transparent)] + pub struct $name: c_int { + /// The module should not generate any messages. + const SILENT = constants::PAM_SILENT; + $($inner)* + } + } } +} - define!( - /// A fictitious constant for testing purposes. - #[cfg(not(pam_impl = "OpenPam"))] - PAM_BAD_CONSTANT = 513; - PAM_BAD_FEATURE = 514; - ); +pam_flags! { + /// Flags for authentication and account management. + AuthnFlags { + /// The module should return [AuthError](ErrorCode::AuthError) + /// if the user has an empty authentication token, rather than + /// allowing them to log in. + const DISALLOW_NULL_AUTHTOK = constants::PAM_DISALLOW_NULL_AUTHTOK; + } +} - define!( - /// A fictitious constant for testing purposes. - #[cfg(not(any(pam_impl = "LinuxPam", pam_impl = "OpenPam")))] - PAM_BAD_ITEM = 515; - PAM_MODULE_UNKNOWN = 516; - ); +pam_flags! { + /// Flags for changing the authentication token. + AuthtokFlags { + /// Indicates that the user's authentication token should + /// only be changed if it is expired. If not passed, + /// the authentication token should be changed unconditionally. + const CHANGE_EXPIRED_AUTHTOK = constants::PAM_CHANGE_EXPIRED_AUTHTOK; - define!( - /// A fictitious constant for testing purposes. - #[cfg(not(pam_impl = "LinuxPam"))] - PAM_CONV_AGAIN = 517; - PAM_INCOMPLETE = 518; - ); + /// Don't check if the password is any good (Sun only). + #[cfg(pam_impl = "Sun")] + const NO_AUTHTOK_CHECK = constants::PAM_NO_AUTHTOK_CHECK; + } +} + +pam_flags! { + /// Common flag(s) shared by all PAM actions. + BaseFlags {} } -bitflags! { - /// The available PAM flags. - /// - /// See `/usr/include/security/_pam_types.h` and - /// See `/usr/include/security/pam_modules.h` for more details. - #[derive(Debug, Default, PartialEq)] - #[repr(transparent)] - pub struct Flags: c_int { - /// The module should not generate any messages. - const SILENT = pam_constants::PAM_SILENT; +#[cfg(feature = "openpam-ext")] +const BAD_CONST: ErrorCode = ErrorCode::BadConstant; +#[cfg(not(feature = "openpam-ext"))] +const BAD_CONST: ErrorCode = ErrorCode::SystemError; - /// The module should return [ErrorCode::AuthError] - /// if the user has an empty authentication token - /// rather than immediately accepting them. - const DISALLOW_NULL_AUTHTOK = pam_constants::PAM_DISALLOW_NULL_AUTHTOK; +macro_rules! flag_enum { + ( + $(#[$m:meta])* + $name:ident { + $( + $(#[$item_m:meta])* + $item_name:ident = $item_value:expr, + )* + } + ) => { + $(#[$m])* + #[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive, IntoPrimitive)] + #[repr(i32)] + pub enum $name { + $( + $(#[$item_m])* + $item_name = $item_value, + )* + } - // Flag used for `set_credentials`. + impl $name { + const ALL_VALUES: i32 = 0 $( | $item_value)*; + + fn split(value: i32) -> Result<(Option<Self>, i32)> { + let me = value & Self::ALL_VALUES; + let them = value & !Self::ALL_VALUES; + let me = match me { + 0 => None, + n => Some(Self::try_from(n).map_err(|_| BAD_CONST)?), + }; + Ok((me, them)) + } + } + } +} - /// Set user credentials for an authentication service. - const ESTABLISH_CREDENTIALS = pam_constants::PAM_ESTABLISH_CRED; - /// Delete user credentials associated with - /// an authentication service. - const DELETE_CREDENTIALS = pam_constants::PAM_DELETE_CRED; - /// Reinitialize user credentials. - const REINITIALIZE_CREDENTIALS = pam_constants::PAM_REINITIALIZE_CRED; - /// Extend the lifetime of user credentials. - const REFRESH_CREDENTIALS = pam_constants::PAM_REFRESH_CRED; +flag_enum! { + /// The credential management action that should take place. + CredAction { + /// Set the user's credentials from this module. Default if unspecified. + Establish = constants::PAM_ESTABLISH_CRED, + /// Revoke the user's credentials established by this module. + Delete = constants::PAM_DELETE_CRED, + /// Fully reinitialize the user's credentials from this module. + Reinitialize = constants::PAM_REINITIALIZE_CRED, + /// Extend the lifetime of the user's credentials from this module. + Refresh = constants::PAM_REFRESH_CRED, + } +} - // Flags used for password changing. +impl CredAction { + /// Separates this enum from the remaining [`BaseFlags`]. + pub fn extract(value: i32) -> Result<(Self, BaseFlags)> { + Self::split(value) + .map(|(act, rest)| (act.unwrap_or_default(), BaseFlags::from_bits_retain(rest))) + } +} - /// 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 = pam_constants::PAM_CHANGE_EXPIRED_AUTHTOK; - /// 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 = pam_constants::PAM_PRELIM_CHECK; - /// 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 = pam_constants::PAM_UPDATE_AUTHTOK; +impl Default for CredAction { + fn default() -> Self { + Self::Establish + } +} + +flag_enum! { + AuthtokAction { + /// This is a preliminary call to check if we're ready to change passwords + /// and that the new password is acceptable. + PreliminaryCheck = constants::PAM_PRELIM_CHECK, + /// You should actually update the password. + Update = constants::PAM_UPDATE_AUTHTOK, + } +} + +impl AuthtokAction { + /// Separates this enum from the remaining [`AuthtokFlags`]. + pub fn extract(value: i32) -> Result<(Self, AuthtokFlags)> { + match Self::split(value)? { + (Some(act), rest) => Ok((act, AuthtokFlags::from_bits_retain(rest))), + (None, _) => Err(BAD_CONST), + } } } @@ -124,41 +168,49 @@ #[non_exhaustive] // C might give us anything! #[repr(i32)] pub enum ErrorCode { - OpenError = pam_constants::PAM_OPEN_ERR, - SymbolError = pam_constants::PAM_SYMBOL_ERR, - ServiceError = pam_constants::PAM_SERVICE_ERR, - SystemError = pam_constants::PAM_SYSTEM_ERR, - BufferError = pam_constants::PAM_BUF_ERR, - PermissionDenied = pam_constants::PAM_PERM_DENIED, - AuthenticationError = pam_constants::PAM_AUTH_ERR, - CredentialsInsufficient = pam_constants::PAM_CRED_INSUFFICIENT, - AuthInfoUnavailable = pam_constants::PAM_AUTHINFO_UNAVAIL, - UserUnknown = pam_constants::PAM_USER_UNKNOWN, - MaxTries = pam_constants::PAM_MAXTRIES, - NewAuthTokRequired = pam_constants::PAM_NEW_AUTHTOK_REQD, - AccountExpired = pam_constants::PAM_ACCT_EXPIRED, - SessionError = pam_constants::PAM_SESSION_ERR, - CredentialsUnavailable = pam_constants::PAM_CRED_UNAVAIL, - CredentialsExpired = pam_constants::PAM_CRED_EXPIRED, - CredentialsError = pam_constants::PAM_CRED_ERR, - NoModuleData = pam_constants::PAM_NO_MODULE_DATA, - ConversationError = pam_constants::PAM_CONV_ERR, - AuthTokError = pam_constants::PAM_AUTHTOK_ERR, - AuthTokRecoveryError = pam_constants::PAM_AUTHTOK_RECOVERY_ERR, - AuthTokLockBusy = pam_constants::PAM_AUTHTOK_LOCK_BUSY, - AuthTokDisableAging = pam_constants::PAM_AUTHTOK_DISABLE_AGING, - TryAgain = pam_constants::PAM_TRY_AGAIN, - Ignore = pam_constants::PAM_IGNORE, - Abort = pam_constants::PAM_ABORT, - AuthTokExpired = pam_constants::PAM_AUTHTOK_EXPIRED, + OpenError = constants::PAM_OPEN_ERR, + SymbolError = constants::PAM_SYMBOL_ERR, + ServiceError = constants::PAM_SERVICE_ERR, + SystemError = constants::PAM_SYSTEM_ERR, + BufferError = constants::PAM_BUF_ERR, + PermissionDenied = constants::PAM_PERM_DENIED, + AuthenticationError = constants::PAM_AUTH_ERR, + CredentialsInsufficient = constants::PAM_CRED_INSUFFICIENT, + AuthInfoUnavailable = constants::PAM_AUTHINFO_UNAVAIL, + UserUnknown = constants::PAM_USER_UNKNOWN, + MaxTries = constants::PAM_MAXTRIES, + NewAuthTokRequired = constants::PAM_NEW_AUTHTOK_REQD, + AccountExpired = constants::PAM_ACCT_EXPIRED, + SessionError = constants::PAM_SESSION_ERR, + CredentialsUnavailable = constants::PAM_CRED_UNAVAIL, + CredentialsExpired = constants::PAM_CRED_EXPIRED, + CredentialsError = constants::PAM_CRED_ERR, + NoModuleData = constants::PAM_NO_MODULE_DATA, + ConversationError = constants::PAM_CONV_ERR, + AuthTokError = constants::PAM_AUTHTOK_ERR, + AuthTokRecoveryError = constants::PAM_AUTHTOK_RECOVERY_ERR, + AuthTokLockBusy = constants::PAM_AUTHTOK_LOCK_BUSY, + AuthTokDisableAging = constants::PAM_AUTHTOK_DISABLE_AGING, + TryAgain = constants::PAM_TRY_AGAIN, + Ignore = constants::PAM_IGNORE, + Abort = constants::PAM_ABORT, + AuthTokExpired = constants::PAM_AUTHTOK_EXPIRED, #[cfg(feature = "basic-ext")] - ModuleUnknown = pam_constants::PAM_MODULE_UNKNOWN, + ModuleUnknown = constants::PAM_MODULE_UNKNOWN, #[cfg(feature = "basic-ext")] - BadItem = pam_constants::PAM_BAD_ITEM, + BadItem = constants::PAM_BAD_ITEM, + #[cfg(feature = "linux-pam-ext")] + ConversationAgain = constants::PAM_CONV_AGAIN, #[cfg(feature = "linux-pam-ext")] - ConversationAgain = pam_constants::PAM_CONV_AGAIN, - #[cfg(feature = "linux-pam-ext")] - Incomplete = pam_constants::PAM_INCOMPLETE, + Incomplete = constants::PAM_INCOMPLETE, + #[cfg(feature = "openpam-ext")] + DomainUnknown = constants::PAM_DOMAIN_UNKNOWN, + #[cfg(feature = "openpam-ext")] + BadHandle = constants::PAM_BAD_HANDLE, + #[cfg(feature = "openpam-ext")] + BadFeature = constants::PAM_BAD_FEATURE, + #[cfg(feature = "openpam-ext")] + BadConstant = constants::PAM_BAD_CONSTANT, } /// A PAM-specific Result type with an [ErrorCode] error. @@ -228,13 +280,35 @@ fn test_enums() { assert_eq!(Ok(()), ErrorCode::result_from(0)); assert_eq!( - pam_constants::PAM_SESSION_ERR, + constants::PAM_SESSION_ERR, ErrorCode::result_to_c::<()>(Err(ErrorCode::SessionError)) ); assert_eq!( Err(ErrorCode::Abort), - ErrorCode::result_from(pam_constants::PAM_ABORT) + ErrorCode::result_from(constants::PAM_ABORT) ); assert_eq!(Err(ErrorCode::SystemError), ErrorCode::result_from(423)); } + + #[test] + fn test_flag_enums() { + AuthtokAction::extract(-1).expect_err("too many set"); + AuthtokAction::extract(0).expect_err("too few set"); + assert_eq!( + Ok(( + AuthtokAction::Update, + AuthtokFlags::from_bits_retain(0x7fff0000) + )), + AuthtokAction::extract(0x7fff0000 | constants::PAM_UPDATE_AUTHTOK) + ); + CredAction::extract(0xffff).expect_err("too many set"); + assert_eq!( + Ok((CredAction::Establish, BaseFlags::empty())), + CredAction::extract(0) + ); + assert_eq!( + Ok((CredAction::Delete, BaseFlags::from_bits_retain(0x55000000))), + CredAction::extract(0x55000000 | constants::PAM_DELETE_CRED) + ); + } }
--- a/src/handle.rs Mon Jul 14 18:56:55 2025 -0400 +++ b/src/handle.rs Tue Jul 15 00:32:24 2025 -0400 @@ -1,7 +1,7 @@ //! The wrapper types and traits for handles into the PAM library. use crate::_doc::{guide, linklist, man7, manbsd, stdlinks}; -use crate::constants::{Flags, Result}; +use crate::constants::{AuthnFlags, AuthtokFlags, Result}; use crate::conv::Conversation; use crate::environ::{EnvironMap, EnvironMapMut}; use crate::items::{getter, Items, ItemsMut}; @@ -119,7 +119,7 @@ /// #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_authenticate")] #[doc = stdlinks!(3 pam_authenticate)] - fn authenticate(&mut self, flags: Flags) -> Result<()>; + fn authenticate(&mut self, flags: AuthnFlags) -> Result<()>; /// Verifies the validity of the user's account (and other stuff). /// @@ -132,7 +132,7 @@ /// #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_acct_mgmt")] #[doc = stdlinks!(3 pam_acct_mgmt)] - fn account_management(&mut self, flags: Flags) -> Result<()>; + fn account_management(&mut self, flags: AuthnFlags) -> Result<()>; /// Changes the authentication token. /// @@ -141,7 +141,7 @@ /// #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_chauthtok")] #[doc = stdlinks!(3 pam_chauthtok)] - fn change_authtok(&mut self, flags: Flags) -> Result<()>; + fn change_authtok(&mut self, flags: AuthtokFlags) -> Result<()>; } /// Functionality of a PAM handle that can be expected by a PAM module.
--- a/src/lib.rs Mon Jul 14 18:56:55 2025 -0400 +++ b/src/lib.rs Tue Jul 15 00:32:24 2025 -0400 @@ -43,7 +43,9 @@ pub use crate::libpam::{LibPamHandle, LibPamTransaction}; #[doc(inline)] pub use crate::{ - constants::{ErrorCode, Flags, Result}, + constants::{ + AuthnFlags, AuthtokAction, AuthtokFlags, BaseFlags, CredAction, ErrorCode, Result, + }, conv::{BinaryData, Conversation, ConversationAdapter}, environ::{EnvironMap, EnvironMapMut}, handle::{ModuleClient, PamShared, Transaction},
--- a/src/libpam/handle.rs Mon Jul 14 18:56:55 2025 -0400 +++ b/src/libpam/handle.rs Tue Jul 15 00:32:24 2025 -0400 @@ -9,7 +9,7 @@ use crate::libpam::items::{LibPamItems, LibPamItemsMut}; use crate::libpam::{items, memory}; use crate::logging::{Level, Location, Logger}; -use crate::{Conversation, EnvironMap, Flags, ModuleClient, Transaction}; +use crate::{AuthnFlags, AuthtokFlags, Conversation, EnvironMap, ModuleClient, Transaction}; use libpam_sys_consts::constants; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::any::TypeId; @@ -153,8 +153,8 @@ } macro_rules! wrap { - (fn $name:ident { $pam_func:ident }) => { - fn $name(&mut self, flags: Flags) -> Result<()> { + (fn $name:ident($ftype:ident) { $pam_func:ident }) => { + fn $name(&mut self, flags: $ftype) -> Result<()> { ErrorCode::result_from(unsafe { libpam_sys::$pam_func((self as *mut Self).cast(), flags.bits()) }) @@ -163,9 +163,9 @@ } impl Transaction for LibPamHandle { - wrap!(fn authenticate { pam_authenticate }); - wrap!(fn account_management { pam_acct_mgmt }); - wrap!(fn change_authtok { pam_chauthtok }); + wrap!(fn authenticate(AuthnFlags) { pam_authenticate }); + wrap!(fn account_management(AuthnFlags) { pam_acct_mgmt }); + wrap!(fn change_authtok(AuthtokFlags) { pam_chauthtok }); } // TODO: pam_setcred - app @@ -233,9 +233,9 @@ } impl<C: Conversation> Transaction for LibPamTransaction<C> { - delegate!(fn authenticate(&mut self, flags: Flags) -> Result<()>); - delegate!(fn account_management(&mut self, flags: Flags) -> Result<()>); - delegate!(fn change_authtok(&mut self, flags: Flags) -> Result<()>); + delegate!(fn authenticate(&mut self, flags: AuthnFlags) -> Result<()>); + delegate!(fn account_management(&mut self, flags: AuthnFlags) -> Result<()>); + delegate!(fn change_authtok(&mut self, flags: AuthtokFlags) -> Result<()>); } impl<C: Conversation> PamShared for LibPamTransaction<C> {
--- a/src/libpam/module.rs Mon Jul 14 18:56:55 2025 -0400 +++ b/src/libpam/module.rs Tue Jul 15 00:32:24 2025 -0400 @@ -11,7 +11,7 @@ /// /// ```no_run /// use nonstick::{ -/// pam_hooks, ConversationAdapter, Flags, LibPamTransaction, ModuleClient, PamModule, +/// pam_hooks, ConversationAdapter, AuthnFlags, LibPamTransaction, ModuleClient, PamModule, /// Result as PamResult, /// }; /// use std::ffi::CStr; @@ -21,7 +21,7 @@ /// pam_hooks!(MyPamModule); /// /// impl<T: ModuleClient> PamModule<T> for MyPamModule { -/// fn authenticate(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { +/// fn authenticate(handle: &mut T, args: Vec<&CStr>, flags: AuthnFlags) -> PamResult<()> { /// let password = handle.authtok(Some("what's your password?".as_ref()))?; /// let response = /// format!("If you say your password is {password:?}, who am I to disagree?"); @@ -29,7 +29,7 @@ /// Ok(()) /// } /// -/// fn account_management(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> 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); @@ -42,96 +42,98 @@ ($ident:ident) => { mod _pam_hooks_scope { use std::ffi::{c_char, c_int, c_void, CStr}; - use $crate::{ErrorCode, Flags, LibPamHandle, PamModule}; + use $crate::{ + AuthnFlags, AuthtokAction, BaseFlags, CredAction, ErrorCode, LibPamHandle, + PamModule, + }; + + macro_rules! handle { + ($pamh:ident) => { + match unsafe { $pamh.cast::<LibPamHandle>().as_mut() } { + Some(handle) => handle, + None => return ErrorCode::Ignore.into(), + } + }; + } #[no_mangle] extern "C" fn pam_sm_acct_mgmt( pamh: *mut c_void, - flags: Flags, + flags: AuthnFlags, argc: c_int, argv: *const *const c_char, ) -> c_int { - if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } { - let args = extract_argv(argc, argv); - ErrorCode::result_to_c(super::$ident::account_management(handle, args, flags)) - } else { - ErrorCode::Ignore as c_int - } + let handle = handle!(pamh); + let args = extract_argv(argc, argv); + ErrorCode::result_to_c(super::$ident::account_management(handle, args, flags)) } #[no_mangle] extern "C" fn pam_sm_authenticate( pamh: *mut c_void, - flags: Flags, + flags: AuthnFlags, argc: c_int, argv: *const *const c_char, ) -> c_int { - if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } { - let args = extract_argv(argc, argv); - ErrorCode::result_to_c(super::$ident::authenticate(handle, args, flags)) - } else { - ErrorCode::Ignore as c_int - } + let handle = handle!(pamh); + let args = extract_argv(argc, argv); + ErrorCode::result_to_c(super::$ident::authenticate(handle, args, flags)) } #[no_mangle] extern "C" fn pam_sm_chauthtok( pamh: *mut c_void, - flags: Flags, + flags: c_int, argc: c_int, argv: *const *const c_char, ) -> c_int { - if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } { - let args = extract_argv(argc, argv); - ErrorCode::result_to_c(super::$ident::change_authtok(handle, args, flags)) - } else { - ErrorCode::Ignore as c_int - } + let handle = handle!(pamh); + let (action, flags) = match AuthtokAction::extract(flags) { + Ok(val) => val, + Err(e) => return e.into(), + }; + let args = extract_argv(argc, argv); + ErrorCode::result_to_c(super::$ident::change_authtok(handle, args, action, flags)) } #[no_mangle] extern "C" fn pam_sm_close_session( pamh: *mut c_void, - flags: Flags, + flags: BaseFlags, argc: c_int, argv: *const *const c_char, ) -> c_int { - if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } { - let args = extract_argv(argc, argv); - ErrorCode::result_to_c(super::$ident::close_session(handle, args, flags)) - } else { - ErrorCode::Ignore as c_int - } + let handle = handle!(pamh); + let args = extract_argv(argc, argv); + ErrorCode::result_to_c(super::$ident::close_session(handle, args, flags)) } #[no_mangle] extern "C" fn pam_sm_open_session( pamh: *mut c_void, - flags: Flags, + flags: BaseFlags, argc: c_int, argv: *const *const c_char, ) -> c_int { + let handle = handle!(pamh); let args = extract_argv(argc, argv); - if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } { - ErrorCode::result_to_c(super::$ident::open_session(handle, args, flags)) - } else { - ErrorCode::Ignore as c_int - } + ErrorCode::result_to_c(super::$ident::open_session(handle, args, flags)) } #[no_mangle] extern "C" fn pam_sm_setcred( pamh: *mut c_void, - flags: Flags, + flags: c_int, argc: c_int, argv: *const *const c_char, ) -> c_int { + let handle = handle!(pamh); + let (action, flags) = match CredAction::extract(flags) { + Ok(val) => val, + Err(e) => return e.into(), + }; let args = extract_argv(argc, argv); - if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } { - ErrorCode::result_to_c(super::$ident::set_credentials(handle, args, flags)) - } else { - ErrorCode::Ignore as c_int - } + ErrorCode::result_to_c(super::$ident::set_credentials(handle, args, action, flags)) } /// Turns `argc`/`argv` into a [Vec] of [CStr]s.
--- a/src/module.rs Mon Jul 14 18:56:55 2025 -0400 +++ b/src/module.rs Tue Jul 15 00:32:24 2025 -0400 @@ -3,7 +3,9 @@ // Temporarily allowed until we get the actual conversation functions hooked up. #![allow(dead_code)] -use crate::constants::{ErrorCode, Flags, Result}; +use crate::constants::{ + AuthnFlags, AuthtokAction, AuthtokFlags, BaseFlags, CredAction, ErrorCode, Result, +}; use crate::handle::ModuleClient; use std::ffi::CStr; @@ -35,13 +37,6 @@ /// 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(())`. @@ -59,7 +54,7 @@ /// 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 T, args: Vec<&CStr>, flags: Flags) -> Result<()> { + fn authenticate(handle: &mut T, args: Vec<&CStr>, flags: AuthnFlags) -> Result<()> { Err(ErrorCode::Ignore) } @@ -79,13 +74,6 @@ /// See [the Module Writer's Guide entry for `pam_sm_acct_mgmt`][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 user should be allowed to log in, return `Ok(())`. @@ -101,7 +89,7 @@ /// - [`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 T, args: Vec<&CStr>, flags: Flags) -> Result<()> { + fn account_management(handle: &mut T, args: Vec<&CStr>, flags: AuthnFlags) -> Result<()> { Err(ErrorCode::Ignore) } @@ -137,7 +125,12 @@ /// - [`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 T, args: Vec<&CStr>, flags: Flags) -> Result<()> { + fn set_credentials( + handle: &mut T, + args: Vec<&CStr>, + action: CredAction, + flags: BaseFlags, + ) -> Result<()> { Err(ErrorCode::Ignore) } @@ -155,18 +148,6 @@ /// 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 @@ -185,7 +166,12 @@ /// - [`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 T, args: Vec<&CStr>, flags: Flags) -> Result<()> { + fn change_authtok( + handle: &mut T, + args: Vec<&CStr>, + action: AuthtokAction, + flags: AuthtokFlags, + ) -> Result<()> { Err(ErrorCode::Ignore) } @@ -196,10 +182,6 @@ /// 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(())`. @@ -209,7 +191,7 @@ /// - [`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 T, args: Vec<&CStr>, flags: Flags) -> Result<()> { + fn open_session(handle: &mut T, args: Vec<&CStr>, flags: BaseFlags) -> Result<()> { Err(ErrorCode::Ignore) } @@ -218,10 +200,6 @@ /// 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(())`. @@ -231,7 +209,7 @@ /// - [`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 T, args: Vec<&CStr>, flags: Flags) -> Result<()> { + fn close_session(handle: &mut T, args: Vec<&CStr>, flags: BaseFlags) -> Result<()> { Err(ErrorCode::Ignore) } }
--- a/testharness/src/bin/testharness.rs Mon Jul 14 18:56:55 2025 -0400 +++ b/testharness/src/bin/testharness.rs Tue Jul 15 00:32:24 2025 -0400 @@ -3,7 +3,9 @@ use nonstick::conv::Exchange; use nonstick::items::Items; use nonstick::libpam::TransactionBuilder; -use nonstick::{Conversation, ErrorCode, Flags, LibPamTransaction, PamShared, Transaction}; +use nonstick::{ + AuthnFlags, AuthtokFlags, Conversation, ErrorCode, LibPamTransaction, PamShared, Transaction, +}; use std::cell::Cell; use std::ffi::OsString; use std::os::unix::ffi::OsStrExt; @@ -93,7 +95,7 @@ ..Default::default() }; let mut tx = harness.start(); - let auth = tx.authenticate(Flags::empty()); + let auth = tx.authenticate(AuthnFlags::empty()); assert_eq!(auth, Err(ErrorCode::UserUnknown)); } @@ -103,19 +105,20 @@ ..Default::default() }; let mut tx = harness.start(); - let auth = tx.authenticate(Flags::empty()); + let auth = tx.authenticate(AuthnFlags::empty()); assert_eq!(auth, Err(ErrorCode::AuthenticationError)); } fn test_correct() { let harness = TestHarness::default(); let mut tx = harness.start(); - tx.authenticate(Flags::empty()).unwrap(); + tx.authenticate(AuthnFlags::empty()).unwrap(); assert_eq!(tx.items().user().unwrap().unwrap(), "updated-in-process"); - let result = tx.account_management(Flags::empty()); + let result = tx.account_management(AuthnFlags::empty()); assert_eq!(result, Err(ErrorCode::NewAuthTokRequired)); harness.changing_password.set(true); - let change = tx.change_authtok(Flags::CHANGE_EXPIRED_AUTHTOK); + let change = tx.change_authtok(AuthtokFlags::CHANGE_EXPIRED_AUTHTOK); assert_eq!(change, Err(ErrorCode::TryAgain)); - tx.change_authtok(Flags::CHANGE_EXPIRED_AUTHTOK).unwrap(); + tx.change_authtok(AuthtokFlags::CHANGE_EXPIRED_AUTHTOK) + .unwrap(); }
--- a/testharness/src/lib.rs Mon Jul 14 18:56:55 2025 -0400 +++ b/testharness/src/lib.rs Tue Jul 15 00:32:24 2025 -0400 @@ -5,14 +5,17 @@ extern crate nonstick; use nonstick::conv::{ErrorMsg, InfoMsg, MaskedQAndA, QAndA}; -use nonstick::{error, info, pam_hooks, ErrorCode, Flags, ModuleClient, PamModule}; +use nonstick::{ + error, info, pam_hooks, AuthnFlags, AuthtokAction, AuthtokFlags, ErrorCode, ModuleClient, + PamModule, +}; use std::ffi::{CStr, OsString}; use std::os::unix::ffi::OsStrExt; struct TestHarness; impl<M: ModuleClient> PamModule<M> for TestHarness { - fn authenticate(handle: &mut M, args: Vec<&CStr>, _: Flags) -> nonstick::Result<()> { + fn authenticate(handle: &mut M, args: Vec<&CStr>, _: AuthnFlags) -> nonstick::Result<()> { let strings: Vec<_> = args.iter().map(|&a| Vec::from(a.to_bytes())).collect(); if strings != vec![Vec::from(b"param"), Vec::from(b"param2")] { return Err(ErrorCode::SystemError); @@ -51,7 +54,7 @@ } } - fn account_management(handle: &mut M, _: Vec<&CStr>, _: Flags) -> nonstick::Result<()> { + fn account_management(handle: &mut M, _: Vec<&CStr>, _: AuthnFlags) -> nonstick::Result<()> { let value: &Cell<i32> = match handle.username(None)?.as_bytes() { b"initial" => return Err(ErrorCode::AccountExpired), b"updated-in-process" => handle.get_module_data("florgus"), @@ -77,26 +80,31 @@ } } - fn change_authtok(handle: &mut M, _: Vec<&CStr>, flags: Flags) -> nonstick::Result<()> { - if flags.contains(Flags::PRELIMINARY_CHECK) { - let password = handle.authtok(None)?; - if password.as_bytes() != b"acceptable" { - return Err(ErrorCode::PermissionDenied); + fn change_authtok( + handle: &mut M, + _: Vec<&CStr>, + action: AuthtokAction, + _flags: AuthtokFlags, + ) -> nonstick::Result<()> { + match action { + AuthtokAction::PreliminaryCheck => { + let password = handle.authtok(None)?; + if password.as_bytes() != b"acceptable" { + return Err(ErrorCode::PermissionDenied); + } + handle.set_module_data("checked_pass", password) } - handle.set_module_data("checked_pass", password) - } else if flags.contains(Flags::UPDATE_AUTHTOK) { - let password = handle.authtok(None)?; - let checked: &OsString = handle - .get_module_data("checked_pass") - .ok_or(ErrorCode::SystemError)?; - if password != *checked { - error!(handle, "password mismatch? {password:?} {checked:?}"); - return Err(ErrorCode::AuthenticationError); + AuthtokAction::Update => { + let password = handle.authtok(None)?; + let checked: &OsString = handle + .get_module_data("checked_pass") + .ok_or(ErrorCode::SystemError)?; + if password != *checked { + error!(handle, "password mismatch? {password:?} {checked:?}"); + return Err(ErrorCode::AuthenticationError); + } + Ok(()) } - Ok(()) - } else { - error!(handle, "invalid flag state: {flags:?}"); - Err(ErrorCode::SystemError) } } }