view src/constants.rs @ 65:8e507c7af9cf default tip

Added tag v0.0.5 for changeset bbe84835d6db
author Paul Fisher <paul@pfish.zone>
date Thu, 22 May 2025 02:08:10 -0400
parents bbe84835d6db
children
line wrap: on
line source

//! Constants and enum values from the PAM library.

use bitflags::bitflags;
use libc::{c_int, c_uint};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use std::any;
use std::marker::PhantomData;

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, PartialEq)]
    #[repr(transparent)]
    pub struct Flags: c_uint {
        /// The module should not generate any messages.
        const SILENT = 0x8000;

        /// The module should return [ErrorCode::AuthError]
        /// if the user has an empty authentication token
        /// rather than immediately accepting them.
        const DISALLOW_NULL_AUTHTOK = 0x0001;

        // Flag used for `set_credentials`.

        /// Set user credentials for an authentication service.
        const ESTABLISH_CREDENTIALS = 0x0002;
        /// Delete user credentials associated with
        /// an authentication service.
        const DELETE_CREDENTIALS = 0x0004;
        /// Reinitialize user credentials.
        const REINITIALIZE_CREDENTIALS = 0x0008;
        /// Extend the lifetime of user credentials.
        const REFRESH_CREDENTIALS = 0x0010;

        // Flags used for password changing.

        /// 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 = 0x0020;

        /// 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 = 0x4000;
        /// 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 = 0x2000;
    }
}

/// The Linux-PAM error return values. Success is an Ok [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)]
#[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 password check failed")]
    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,
}

/// A PAM-specific Result type with an [ErrorCode] error.
pub type Result<T> = std::result::Result<T, ErrorCode>;

impl ErrorCode {
    /// Converts this [Result] into a C-compatible result code.
    pub fn result_to_c<T>(value: Result<T>) -> c_int {
        match value {
            Ok(_) => 0, // PAM_SUCCESS
            Err(otherwise) => otherwise.into(),
        }
    }

    /// Converts a C result code into a [Result], with success as Ok.
    /// Invalid values are returned as a [Self::SystemError].
    pub fn result_from(value: c_int) -> Result<()> {
        match value {
            0 => Ok(()),
            value => Err(value.try_into().unwrap_or(Self::SystemError)),
        }
    }
}

impl TryFrom<c_int> for ErrorCode {
    type Error = InvalidEnum<Self>;

    fn try_from(value: c_int) -> std::result::Result<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(thiserror::Error)]
#[error("{0} is not a valid {type}", type = any::type_name::<T>())]
#[derive(Debug, PartialEq)]
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)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[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!(Err(ErrorCode::SystemError), ErrorCode::result_from(423));
        assert!(InvalidEnum::<ErrorCode>(33, PhantomData)
            .to_string()
            .starts_with("33 is not a valid "));
    }
}