view 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 source

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.

// The Linux-PAM flags
// see /usr/include/security/_pam_types.h
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;
    }
}

/// 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,
}

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`.
#[allow(non_camel_case_types, dead_code)]
#[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")]
    OpenError = 1,
    #[error("symbol not found")]
    SymbolError = 2,
    #[error("error in service module")]
    ServiceError = 3,
    #[error("system error")]
    SystemError = 4,
    #[error("memory buffer error")]
    BufferError = 5,
    #[error("permission denied")]
    PermissionDenied = 6,
    #[error("authentication failure")]
    AuthenticationError = 7,
    #[error("cannot access authentication data due to insufficient credentials")]
    CredentialsInsufficient = 8,
    #[error("underlying authentication service cannot retrieve authentication information")]
    AuthInfoUnavailable = 9,
    #[error("user not known to the underlying authentication module")]
    UserUnknown = 10,
    #[error("retry limit reached; do not attempt further")]
    MaxTries = 11,
    #[error("new authentication token required")]
    NewAuthTokRequired = 12,
    #[error("user account has expired")]
    AccountExpired = 13,
    #[error("cannot make/remove an entry for the specified session")]
    SessionError = 14,
    #[error("underlying authentication service cannot retrieve user credentials")]
    CredentialsUnavailable = 15,
    #[error("user credentials expired")]
    CredentialsExpired = 16,
    #[error("failure setting user credentials")]
    CredentialsError = 17,
    #[error("no module-specific data is present")]
    NoModuleData = 18,
    #[error("conversation error")]
    ConversationError = 19,
    #[error("authentication token manipulation error")]
    AuthTokError = 20,
    #[error("authentication information cannot be recovered")]
    AuthTokRecoveryError = 21,
    #[error("authentication token lock busy")]
    AuthTokLockBusy = 22,
    #[error("authentication token aging disabled")]
    AuthTokDisableAging = 23,
    #[error("preliminary check by password service")]
    TryAgain = 24,
    #[error("ignore underlying account module, regardless of control flag")]
    Ignore = 25,
    #[error("critical error; this module should fail now")]
    Abort = 26,
    #[error("authentication token has expired")]
    AuthTokExpired = 27,
    #[error("module is not known")]
    ModuleUnknown = 28,
    #[error("bad item passed to pam_[whatever]_item")]
    BadItem = 29,
    #[error("conversation function is event-driven and data is not available yet")]
    ConversationAgain = 30,
    #[error("call this function again to complete authentication stack")]
    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)
    }
}