diff src/constants.rs @ 80:5aa1a010f1e8

Start using PAM headers; improve owned/borrowed distinction. - Uses bindgen to generate bindings (only if needed). - Gets the story together on owned vs. borrowed handles. - Reduces number of mutable borrows in handle operation (since `PamHandle` is neither `Send` nor `Sync`, we never have to worry about thread safety. - Improves a bunch of macros so we don't have our own special syntax for docs. - Implement question indirection for standard XSSO PAM implementations.
author Paul Fisher <paul@pfish.zone>
date Tue, 10 Jun 2025 01:09:30 -0400
parents 351bdc13005e
children a638a45e5f1f
line wrap: on
line diff
--- a/src/constants.rs	Sun Jun 08 04:21:58 2025 -0400
+++ b/src/constants.rs	Tue Jun 10 01:09:30 2025 -0400
@@ -1,13 +1,79 @@
 //! Constants and enum values from the PAM library.
 
+#[cfg(feature = "link")]
+use crate::libpam::pam_ffi;
 use bitflags::bitflags;
-use libc::{c_int, c_uint};
-use num_derive::FromPrimitive;
-use num_traits::FromPrimitive;
-use std::any;
-use std::marker::PhantomData;
+use libc::c_int;
+use num_enum::{IntoPrimitive, TryFromPrimitive};
 use std::result::Result as StdResult;
 
+/// Arbitrary values for PAM constants when not linking against system PAM.
+///
+/// **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.**
+#[cfg(not(feature = "link"))]
+mod ffi {
+    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)+};
+    }
+    const fn bit(n: i8) -> i32 {
+        1 << n
+    }
+    define!(
+        PAM_SILENT = bit(13),
+        PAM_DISALLOW_NULL_AUTHTOK = bit(14),
+        PAM_ESTABLISH_CRED = bit(15),
+        PAM_DELETE_CRED = bit(16),
+        PAM_REINITIALIZE_CRED = bit(17),
+        PAM_REFRESH_CRED = bit(18),
+        PAM_CHANGE_EXPIRED_AUTHTOK = bit(19),
+        PAM_PRELIM_CHECK = bit(20),
+        PAM_UPDATE_AUTHTOK = bit(21)
+    );
+
+    define!(
+        PAM_ABORT = 513,
+        PAM_ACCT_EXPIRED = 514,
+        PAM_AUTHINFO_UNAVAIL = 515,
+        PAM_AUTHTOK_DISABLE_AGING = 516,
+        PAM_AUTHTOK_ERR = 517,
+        PAM_AUTHTOK_EXPIRED = 518,
+        PAM_AUTHTOK_LOCK_BUSY = 519,
+        PAM_AUTHTOK_RECOVERY_ERR = 520,
+        PAM_AUTH_ERR = 521,
+        PAM_BAD_ITEM = 522,
+        PAM_BUF_ERR = 533,
+        PAM_CONV_AGAIN = 534,
+        PAM_CONV_ERR = 535,
+        PAM_CRED_ERR = 536,
+        PAM_CRED_EXPIRED = 537,
+        PAM_CRED_INSUFFICIENT = 538,
+        PAM_CRED_UNAVAIL = 539,
+        PAM_IGNORE = 540,
+        PAM_INCOMPLETE = 541,
+        PAM_MAXTRIES = 542,
+        PAM_MODULE_UNKNOWN = 543,
+        PAM_NEW_AUTHTOK_REQD = 544,
+        PAM_NO_MODULE_DATA = 545,
+        PAM_OPEN_ERR = 546,
+        PAM_PERM_DENIED = 547,
+        PAM_SERVICE_ERR = 548,
+        PAM_SESSION_ERR = 549,
+        PAM_SYMBOL_ERR = 550,
+        PAM_SYSTEM_ERR = 551,
+        PAM_TRY_AGAIN = 552,
+        PAM_USER_UNKNOWN = 553
+    );
+}
+
 bitflags! {
     /// The available PAM flags.
     ///
@@ -15,26 +81,26 @@
     /// See `/usr/include/security/pam_modules.h` for more details.
     #[derive(Debug, PartialEq)]
     #[repr(transparent)]
-    pub struct Flags: c_uint {
+    pub struct Flags: c_int {
         /// The module should not generate any messages.
-        const SILENT = 0x8000;
+        const SILENT = pam_ffi::PAM_SILENT;
 
         /// The module should return [ErrorCode::AuthError]
         /// if the user has an empty authentication token
         /// rather than immediately accepting them.
-        const DISALLOW_NULL_AUTHTOK = 0x0001;
+        const DISALLOW_NULL_AUTHTOK = pam_ffi::PAM_DISALLOW_NULL_AUTHTOK;
 
         // Flag used for `set_credentials`.
 
         /// Set user credentials for an authentication service.
-        const ESTABLISH_CREDENTIALS = 0x0002;
+        const ESTABLISH_CREDENTIALS = pam_ffi::PAM_ESTABLISH_CRED;
         /// Delete user credentials associated with
         /// an authentication service.
-        const DELETE_CREDENTIALS = 0x0004;
+        const DELETE_CREDENTIALS = pam_ffi::PAM_DELETE_CRED;
         /// Reinitialize user credentials.
-        const REINITIALIZE_CREDENTIALS = 0x0008;
+        const REINITIALIZE_CREDENTIALS = pam_ffi::PAM_REINITIALIZE_CRED;
         /// Extend the lifetime of user credentials.
-        const REFRESH_CREDENTIALS = 0x0010;
+        const REFRESH_CREDENTIALS = pam_ffi::PAM_REFRESH_CRED;
 
         // Flags used for password changing.
 
@@ -43,7 +109,7 @@
         /// the password service should update all passwords.
         ///
         /// This flag is only used by `change_authtok`.
-        const CHANGE_EXPIRED_AUTHTOK = 0x0020;
+        const CHANGE_EXPIRED_AUTHTOK = pam_ffi::PAM_CHANGE_EXPIRED_AUTHTOK;
 
         /// This is a preliminary check for password changing.
         /// The password should not be changed.
@@ -52,7 +118,7 @@
         /// Applications may not use this flag.
         ///
         /// This flag is only used by `change_authtok`.
-        const PRELIMINARY_CHECK = 0x4000;
+        const PRELIMINARY_CHECK = pam_ffi::PAM_PRELIM_CHECK;
         /// The password should actuallyPR be updated.
         /// This and [Self::PRELIMINARY_CHECK] are mutually exclusive.
         ///
@@ -60,7 +126,7 @@
         /// Applications may not use this flag.
         ///
         /// This flag is only used by `change_authtok`.
-        const UPDATE_AUTHTOK = 0x2000;
+        const UPDATE_AUTHTOK = pam_ffi::PAM_UPDATE_AUTHTOK;
     }
 }
 
@@ -70,71 +136,72 @@
 /// For more detailed information, see
 /// `/usr/include/security/_pam_types.h`.
 #[allow(non_camel_case_types, dead_code)]
-#[derive(Copy, Clone, Debug, PartialEq, thiserror::Error, FromPrimitive)]
+#[derive(Copy, Clone, Debug, PartialEq, thiserror::Error, TryFromPrimitive, IntoPrimitive)]
 #[non_exhaustive] // C might give us anything!
+#[repr(i32)]
 pub enum ErrorCode {
     #[error("dlopen() failure when dynamically loading a service module")]
-    OpenError = 1,
+    OpenError = pam_ffi::PAM_OPEN_ERR,
     #[error("symbol not found")]
-    SymbolError = 2,
+    SymbolError = pam_ffi::PAM_SYMBOL_ERR,
     #[error("error in service module")]
-    ServiceError = 3,
+    ServiceError = pam_ffi::PAM_SERVICE_ERR,
     #[error("system error")]
-    SystemError = 4,
+    SystemError = pam_ffi::PAM_SYSTEM_ERR,
     #[error("memory buffer error")]
-    BufferError = 5,
+    BufferError = pam_ffi::PAM_BUF_ERR,
     #[error("permission denied")]
-    PermissionDenied = 6,
+    PermissionDenied = pam_ffi::PAM_PERM_DENIED,
     #[error("authentication failure")]
-    AuthenticationError = 7,
+    AuthenticationError = pam_ffi::PAM_AUTH_ERR,
     #[error("cannot access authentication data due to insufficient credentials")]
-    CredentialsInsufficient = 8,
+    CredentialsInsufficient = pam_ffi::PAM_CRED_INSUFFICIENT,
     #[error("underlying authentication service cannot retrieve authentication information")]
-    AuthInfoUnavailable = 9,
+    AuthInfoUnavailable = pam_ffi::PAM_AUTHINFO_UNAVAIL,
     #[error("user not known to the underlying authentication module")]
-    UserUnknown = 10,
+    UserUnknown = pam_ffi::PAM_USER_UNKNOWN,
     #[error("retry limit reached; do not attempt further")]
-    MaxTries = 11,
+    MaxTries = pam_ffi::PAM_MAXTRIES,
     #[error("new authentication token required")]
-    NewAuthTokRequired = 12,
+    NewAuthTokRequired = pam_ffi::PAM_NEW_AUTHTOK_REQD,
     #[error("user account has expired")]
-    AccountExpired = 13,
+    AccountExpired = pam_ffi::PAM_ACCT_EXPIRED,
     #[error("cannot make/remove an entry for the specified session")]
-    SessionError = 14,
+    SessionError = pam_ffi::PAM_SESSION_ERR,
     #[error("underlying authentication service cannot retrieve user credentials")]
-    CredentialsUnavailable = 15,
+    CredentialsUnavailable = pam_ffi::PAM_CRED_UNAVAIL,
     #[error("user credentials expired")]
-    CredentialsExpired = 16,
+    CredentialsExpired = pam_ffi::PAM_CRED_EXPIRED,
     #[error("failure setting user credentials")]
-    CredentialsError = 17,
+    CredentialsError = pam_ffi::PAM_CRED_ERR,
     #[error("no module-specific data is present")]
-    NoModuleData = 18,
+    NoModuleData = pam_ffi::PAM_NO_MODULE_DATA,
     #[error("conversation error")]
-    ConversationError = 19,
+    ConversationError = pam_ffi::PAM_CONV_ERR,
     #[error("authentication token manipulation error")]
-    AuthTokError = 20,
+    AuthTokError = pam_ffi::PAM_AUTHTOK_ERR,
     #[error("authentication information cannot be recovered")]
-    AuthTokRecoveryError = 21,
+    AuthTokRecoveryError = pam_ffi::PAM_AUTHTOK_RECOVERY_ERR,
     #[error("authentication token lock busy")]
-    AuthTokLockBusy = 22,
+    AuthTokLockBusy = pam_ffi::PAM_AUTHTOK_LOCK_BUSY,
     #[error("authentication token aging disabled")]
-    AuthTokDisableAging = 23,
+    AuthTokDisableAging = pam_ffi::PAM_AUTHTOK_DISABLE_AGING,
     #[error("preliminary password check failed")]
-    TryAgain = 24,
+    TryAgain = pam_ffi::PAM_TRY_AGAIN,
     #[error("ignore underlying account module, regardless of control flag")]
-    Ignore = 25,
+    Ignore = pam_ffi::PAM_IGNORE,
     #[error("critical error; this module should fail now")]
-    Abort = 26,
+    Abort = pam_ffi::PAM_ABORT,
     #[error("authentication token has expired")]
-    AuthTokExpired = 27,
+    AuthTokExpired = pam_ffi::PAM_AUTHTOK_EXPIRED,
     #[error("module is not known")]
-    ModuleUnknown = 28,
+    ModuleUnknown = pam_ffi::PAM_MODULE_UNKNOWN,
     #[error("bad item passed to pam_[whatever]_item")]
-    BadItem = 29,
+    BadItem = pam_ffi::PAM_BAD_ITEM,
     #[error("conversation function is event-driven and data is not available yet")]
-    ConversationAgain = 30,
+    ConversationAgain = pam_ffi::PAM_CONV_AGAIN,
     #[error("call this function again to complete authentication stack")]
-    Incomplete = 31,
+    Incomplete = pam_ffi::PAM_INCOMPLETE,
 }
 
 /// A PAM-specific Result type with an [ErrorCode] error.
@@ -159,37 +226,6 @@
     }
 }
 
-impl TryFrom<c_int> for ErrorCode {
-    type Error = InvalidEnum<Self>;
-
-    fn try_from(value: c_int) -> StdResult<Self, Self::Error> {
-        Self::from_i32(value).ok_or(value.into())
-    }
-}
-
-impl From<ErrorCode> for c_int {
-    fn from(val: ErrorCode) -> Self {
-        val as Self
-    }
-}
-
-/// Error returned when attempting to coerce an invalid C integer into an enum.
-#[derive(Debug, PartialEq, thiserror::Error)]
-#[error("{0} is not a valid {type}", type = any::type_name::<T>())]
-pub struct InvalidEnum<T>(c_int, PhantomData<T>);
-
-impl<T> From<InvalidEnum<T>> for c_int {
-    fn from(value: InvalidEnum<T>) -> Self {
-        value.0
-    }
-}
-
-impl<T> From<c_int> for InvalidEnum<T> {
-    fn from(value: c_int) -> Self {
-        Self(value, PhantomData)
-    }
-}
-
 /// Returned when text that should not have any `\0` bytes in it does.
 /// Analogous to [`std::ffi::NulError`], but the data it was created from
 /// is borrowed.
@@ -199,13 +235,15 @@
 
     #[test]
     fn test_enums() {
-        assert_eq!(Ok(ErrorCode::ServiceError), 3.try_into());
-        assert_eq!(Err(InvalidEnum::from(999)), ErrorCode::try_from(999));
         assert_eq!(Ok(()), ErrorCode::result_from(0));
-        assert_eq!(Err(ErrorCode::Abort), ErrorCode::result_from(26));
+        assert_eq!(
+            pam_ffi::PAM_BAD_ITEM,
+            ErrorCode::result_to_c::<()>(Err(ErrorCode::BadItem))
+        );
+        assert_eq!(
+            Err(ErrorCode::Abort),
+            ErrorCode::result_from(pam_ffi::PAM_ABORT)
+        );
         assert_eq!(Err(ErrorCode::SystemError), ErrorCode::result_from(423));
-        assert!(InvalidEnum::<ErrorCode>(33, PhantomData)
-            .to_string()
-            .starts_with("33 is not a valid "));
     }
 }