diff src/constants.rs @ 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 4b3a5095f68c
children f052e2417195
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)
+        );
+    }
 }