diff src/constants.rs @ 90:f6186e41399b

Miscellaneous fixes and cleanup: - Rename `get_user` to `username` and `get_authtok` to `authtok`. - Use pam_strerror for error messages. - Add library linkage to build.rs (it was missing???).
author Paul Fisher <paul@pfish.zone>
date Sat, 14 Jun 2025 09:30:16 -0400
parents 05291b601f0a
children 5ddbcada30f2
line wrap: on
line diff
--- a/src/constants.rs	Fri Jun 13 05:22:48 2025 -0400
+++ b/src/constants.rs	Sat Jun 14 09:30:16 2025 -0400
@@ -9,7 +9,10 @@
 use bitflags::bitflags;
 use libc::c_int;
 use num_enum::{IntoPrimitive, TryFromPrimitive};
+use std::error::Error;
 use std::ffi::c_uint;
+use std::fmt;
+use std::fmt::{Display, Formatter};
 use std::result::Result as StdResult;
 
 /// Arbitrary values for PAM constants when not linking against system PAM.
@@ -77,6 +80,10 @@
         PAM_TRY_AGAIN = 552,
         PAM_USER_UNKNOWN = 553
     );
+
+    fn strerror(val: c_uint) -> Option<&'static str> {
+        None
+    }
 }
 
 bitflags! {
@@ -140,79 +147,59 @@
 /// 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, TryFromPrimitive, IntoPrimitive)]
+#[derive(Copy, Clone, Debug, PartialEq, TryFromPrimitive, IntoPrimitive)]
 #[non_exhaustive] // C might give us anything!
 #[repr(u32)]
 pub enum ErrorCode {
-    #[error("dlopen() failure when dynamically loading a service module")]
     OpenError = pam_ffi::PAM_OPEN_ERR,
-    #[error("symbol not found")]
     SymbolError = pam_ffi::PAM_SYMBOL_ERR,
-    #[error("error in service module")]
     ServiceError = pam_ffi::PAM_SERVICE_ERR,
-    #[error("system error")]
     SystemError = pam_ffi::PAM_SYSTEM_ERR,
-    #[error("memory buffer error")]
     BufferError = pam_ffi::PAM_BUF_ERR,
-    #[error("permission denied")]
     PermissionDenied = pam_ffi::PAM_PERM_DENIED,
-    #[error("authentication failure")]
     AuthenticationError = pam_ffi::PAM_AUTH_ERR,
-    #[error("cannot access authentication data due to insufficient credentials")]
     CredentialsInsufficient = pam_ffi::PAM_CRED_INSUFFICIENT,
-    #[error("underlying authentication service cannot retrieve authentication information")]
     AuthInfoUnavailable = pam_ffi::PAM_AUTHINFO_UNAVAIL,
-    #[error("user not known to the underlying authentication module")]
     UserUnknown = pam_ffi::PAM_USER_UNKNOWN,
-    #[error("retry limit reached; do not attempt further")]
     MaxTries = pam_ffi::PAM_MAXTRIES,
-    #[error("new authentication token required")]
     NewAuthTokRequired = pam_ffi::PAM_NEW_AUTHTOK_REQD,
-    #[error("user account has expired")]
     AccountExpired = pam_ffi::PAM_ACCT_EXPIRED,
-    #[error("cannot make/remove an entry for the specified session")]
     SessionError = pam_ffi::PAM_SESSION_ERR,
-    #[error("underlying authentication service cannot retrieve user credentials")]
     CredentialsUnavailable = pam_ffi::PAM_CRED_UNAVAIL,
-    #[error("user credentials expired")]
     CredentialsExpired = pam_ffi::PAM_CRED_EXPIRED,
-    #[error("failure setting user credentials")]
     CredentialsError = pam_ffi::PAM_CRED_ERR,
-    #[error("no module-specific data is present")]
     NoModuleData = pam_ffi::PAM_NO_MODULE_DATA,
-    #[error("conversation error")]
     ConversationError = pam_ffi::PAM_CONV_ERR,
-    #[error("authentication token manipulation error")]
     AuthTokError = pam_ffi::PAM_AUTHTOK_ERR,
-    #[error("authentication information cannot be recovered")]
     AuthTokRecoveryError = pam_ffi::PAM_AUTHTOK_RECOVERY_ERR,
-    #[error("authentication token lock busy")]
     AuthTokLockBusy = pam_ffi::PAM_AUTHTOK_LOCK_BUSY,
-    #[error("authentication token aging disabled")]
     AuthTokDisableAging = pam_ffi::PAM_AUTHTOK_DISABLE_AGING,
-    #[error("preliminary password check failed")]
     TryAgain = pam_ffi::PAM_TRY_AGAIN,
-    #[error("ignore underlying account module, regardless of control flag")]
     Ignore = pam_ffi::PAM_IGNORE,
-    #[error("critical error; this module should fail now")]
     Abort = pam_ffi::PAM_ABORT,
-    #[error("authentication token has expired")]
     AuthTokExpired = pam_ffi::PAM_AUTHTOK_EXPIRED,
-    #[error("module is not known")]
     ModuleUnknown = pam_ffi::PAM_MODULE_UNKNOWN,
-    #[error("bad item passed to pam_[whatever]_item")]
     BadItem = pam_ffi::PAM_BAD_ITEM,
     #[cfg(feature = "linux-pam-extensions")]
-    #[error("conversation function is event-driven and data is not available yet")]
     ConversationAgain = pam_ffi::PAM_CONV_AGAIN,
     #[cfg(feature = "linux-pam-extensions")]
-    #[error("call this function again to complete authentication stack")]
     Incomplete = pam_ffi::PAM_INCOMPLETE,
 }
 
 /// A PAM-specific Result type with an [ErrorCode] error.
 pub type Result<T> = StdResult<T, ErrorCode>;
 
+impl Display for ErrorCode {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        match pam_ffi::strerror((*self).into()) {
+            Some(err) => f.write_str(err),
+            None => self.fmt_internal(f),
+        }
+    }
+}
+
+impl Error for ErrorCode {}
+
 impl ErrorCode {
     /// Converts this [Result] into a C-compatible result code.
     pub fn result_to_c<T>(value: Result<T>) -> c_int {
@@ -230,6 +217,11 @@
             value => Err((value as u32).try_into().unwrap_or(Self::SystemError)),
         }
     }
+
+    /// A basic Display implementation for if we don't link against PAM.
+    fn fmt_internal(self, f: &mut Formatter<'_>) -> fmt::Result {
+        write!(f, "PAM error: {self:?} ({n})", n = self as c_uint)
+    }
 }
 
 /// Returned when text that should not have any `\0` bytes in it does.