diff src/constants.rs @ 56:daa2cde64601

Big big refactor. Probably should have been multiple changes. - Makes FFI safer by explicitly specifying c_int in calls. - Uses ToPrimitive/FromPrimitive to make this easier. - Pulls PamFlag variables into a bitflags! struct. - Pulls PamMessageStyle variables into an enum. - Renames ResultCode to ErrorCode. - Switches from PAM_SUCCESS to using a Result<(), ErrorCode>. - Uses thiserror to make ErrorCode into an Error. - Gets rid of pam_try! because now we have Results. - Expands some names (e.g. Conv to Conversation). - Adds more doc comments. - Returns passwords as a SecureString, to avoid unnecessarily keeping it around in memory.
author Paul Fisher <paul@pfish.zone>
date Sun, 04 May 2025 02:56:55 -0400
parents 676675c3d434
children 3f4a77aa88be
line wrap: on
line diff
--- a/src/constants.rs	Sun May 04 00:58:04 2025 -0400
+++ b/src/constants.rs	Sun May 04 02:56:55 2025 -0400
@@ -1,99 +1,144 @@
+use bitflags::bitflags;
 use libc::{c_int, c_uint};
-
+use num_derive::{FromPrimitive, ToPrimitive};
+use num_traits::{FromPrimitive, ToPrimitive};
 // TODO: Import constants from C header file at compile time.
 
-pub type PamFlag = c_uint;
-pub type PamItemType = c_int;
-pub type PamMessageStyle = c_int;
-
 // The Linux-PAM flags
 // see /usr/include/security/_pam_types.h
-pub const PAM_SILENT: PamFlag = 0x8000;
-pub const PAM_DISALLOW_NULL_AUTHTOK: PamFlag = 0x0001;
-pub const PAM_ESTABLISH_CRED: PamFlag = 0x0002;
-pub const PAM_DELETE_CRED: PamFlag = 0x0004;
-pub const PAM_REINITIALIZE_CRED: PamFlag = 0x0008;
-pub const PAM_REFRESH_CRED: PamFlag = 0x0010;
-pub const PAM_CHANGE_EXPIRED_AUTHTOK: PamFlag = 0x0020;
+bitflags! {
+    #[derive(Debug, PartialEq)]
+    #[repr(transparent)]
+    pub struct Flags: c_uint {
+        const SILENT = 0x8000;
+        const DISALLOW_NULL_AUTHTOK = 0x0001;
+        const ESTABLISH_CRED = 0x0002;
+        const DELETE_CRED = 0x0004;
+        const REINITIALIZE_CRED = 0x0008;
+        const REFRESH_CRED = 0x0010;
+        const CHANGE_EXPIRED_AUTHTOK= 0x0020;
+    }
+}
 
-// Message styles
-pub const PAM_PROMPT_ECHO_OFF: PamMessageStyle = 1;
-pub const PAM_PROMPT_ECHO_ON: PamMessageStyle = 2;
-pub const PAM_ERROR_MSG: PamMessageStyle = 3;
-pub const PAM_TEXT_INFO: PamMessageStyle = 4;
-/// yes/no/maybe conditionals
-pub const PAM_RADIO_TYPE: PamMessageStyle = 5;
-pub const PAM_BINARY_PROMPT: PamMessageStyle = 7;
+/// Styles of message that are shown to the user.
+#[derive(Debug, PartialEq, FromPrimitive, ToPrimitive)]
+#[non_exhaustive] // non-exhaustive because C might give us back anything!
+pub enum MessageStyle {
+    /// Requests information from the user; will be masked when typing.
+    PromptEchoOff = 1,
+    /// Requests information from the user; will not be masked.
+    PromptEchoOn = 2,
+    /// An error message.
+    ErrorMsg = 3,
+    /// An informational message.
+    TextInfo = 4,
+    /// Yes/No/Maybe conditionals. Linux-PAM specific.
+    RadioType = 5,
+    /// For server–client non-human interaction.
+    /// NOT part of the X/Open PAM specification.
+    BinaryPrompt = 7,
+}
 
-/// The Linux-PAM return values.
+impl From<MessageStyle> for c_int {
+    fn from(val: MessageStyle) -> Self {
+        val.to_i32().unwrap_or(0)
+    }
+}
+
+/// The Linux-PAM error return values.
+/// Success is instead represented by the `Ok` entry of a `Result`.
+/// Most abbreviations (except `AuthTok` and `Max`) are now full words.
 /// For more detailed information, see
-/// /usr/include/security/_pam_types.h
+/// `/usr/include/security/_pam_types.h`.
 #[allow(non_camel_case_types, dead_code)]
-#[derive(Debug, PartialEq, thiserror::Error)]
-#[repr(C)]
-pub enum PamResultCode {
-    #[error("Not an error")]
-    PAM_SUCCESS = 0,
+#[derive(Copy, Clone, Debug, PartialEq, thiserror::Error, FromPrimitive, ToPrimitive)]
+#[non_exhaustive] // C might give us anything!
+pub enum ErrorCode {
     #[error("dlopen() failure when dynamically loading a service module")]
-    PAM_OPEN_ERR = 1,
+    OpenError = 1,
     #[error("symbol not found")]
-    PAM_SYMBOL_ERR = 2,
+    SymbolError = 2,
     #[error("error in service module")]
-    PAM_SERVICE_ERR = 3,
+    ServiceError = 3,
     #[error("system error")]
-    PAM_SYSTEM_ERR = 4,
+    SystemError = 4,
     #[error("memory buffer error")]
-    PAM_BUF_ERR = 5,
+    BufferError = 5,
     #[error("permission denied")]
-    PAM_PERM_DENIED = 6,
+    PermissionDenied = 6,
     #[error("authentication failure")]
-    PAM_AUTH_ERR = 7,
+    AuthenticationError = 7,
     #[error("cannot access authentication data due to insufficient credentials")]
-    PAM_CRED_INSUFFICIENT = 8,
+    CredentialsInsufficient = 8,
     #[error("underlying authentication service cannot retrieve authentication information")]
-    PAM_AUTHINFO_UNAVAIL = 9,
+    AuthInfoUnavailable = 9,
     #[error("user not known to the underlying authentication module")]
-    PAM_USER_UNKNOWN = 10,
+    UserUnknown = 10,
     #[error("retry limit reached; do not attempt further")]
-    PAM_MAXTRIES = 11,
+    MaxTries = 11,
     #[error("new authentication token required")]
-    PAM_NEW_AUTHTOK_REQD = 12,
+    NewAuthTokRequired = 12,
     #[error("user account has expired")]
-    PAM_ACCT_EXPIRED = 13,
+    AccountExpired = 13,
     #[error("cannot make/remove an entry for the specified session")]
-    PAM_SESSION_ERR = 14,
+    SessionError = 14,
     #[error("underlying authentication service cannot retrieve user credentials")]
-    PAM_CRED_UNAVAIL = 15,
+    CredentialsUnavailable = 15,
     #[error("user credentials expired")]
-    PAM_CRED_EXPIRED = 16,
+    CredentialsExpired = 16,
     #[error("failure setting user credentials")]
-    PAM_CRED_ERR = 17,
+    CredentialsError = 17,
     #[error("no module-specific data is present")]
-    PAM_NO_MODULE_DATA = 18,
+    NoModuleData = 18,
     #[error("conversation error")]
-    PAM_CONV_ERR = 19,
+    ConversationError = 19,
     #[error("authentication token manipulation error")]
-    PAM_AUTHTOK_ERR = 20,
+    AuthTokError = 20,
     #[error("authentication information cannot be recovered")]
-    PAM_AUTHTOK_RECOVERY_ERR = 21,
+    AuthTokRecoveryError = 21,
     #[error("authentication token lock busy")]
-    PAM_AUTHTOK_LOCK_BUSY = 22,
+    AuthTokLockBusy = 22,
     #[error("authentication token aging disabled")]
-    PAM_AUTHTOK_DISABLE_AGING = 23,
+    AuthTokDisableAging = 23,
     #[error("preliminary check by password service")]
-    PAM_TRY_AGAIN = 24,
+    TryAgain = 24,
     #[error("ignore underlying account module, regardless of control flag")]
-    PAM_IGNORE = 25,
+    Ignore = 25,
     #[error("critical error; this module should fail now")]
-    PAM_ABORT = 26,
+    Abort = 26,
     #[error("authentication token has expired")]
-    PAM_AUTHTOK_EXPIRED = 27,
+    AuthTokExpired = 27,
     #[error("module is not known")]
-    PAM_MODULE_UNKNOWN = 28,
+    ModuleUnknown = 28,
     #[error("bad item passed to pam_[whatever]_item")]
-    PAM_BAD_ITEM = 29,
+    BadItem = 29,
     #[error("conversation function is event-driven and data is not available yet")]
-    PAM_CONV_AGAIN = 30,
+    ConversationAgain = 30,
     #[error("call this function again to complete authentication stack")]
-    PAM_INCOMPLETE = 31,
+    Incomplete = 31,
 }
+
+pub type PamResult<T> = Result<T, ErrorCode>;
+impl ErrorCode {
+    /// Converts a PamResult into the result code that C wants.
+    pub fn result_to_c(value: PamResult<()>) -> c_int {
+        match value {
+            Ok(_) => 0, // PAM_SUCCESS
+            Err(otherwise) => otherwise.into(),
+        }
+    }
+
+    /// Converts a C result code into a PamResult, with success as Ok.
+    pub fn result_from(value: c_int) -> PamResult<()> {
+        match value {
+            0 => Ok(()),
+            value => Err(Self::from_i64(value as i64).unwrap_or(Self::ConversationError))
+        }
+    }
+}
+
+impl From<ErrorCode> for c_int {
+    fn from(val: ErrorCode) -> Self {
+        val.to_i32().unwrap_or(0)
+    }
+}