view src/constants.rs @ 86:23162cd399aa

fix THE REST OF THE CONSTANTS
author Paul Fisher <paul@pfish.zone>
date Tue, 10 Jun 2025 02:43:31 -0400
parents 5e14bb093851
children 05291b601f0a
line wrap: on
line source

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

#[cfg(feature = "link")]
use crate::libpam::pam_ffi;
use bitflags::bitflags;
use libc::c_int;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::ffi::c_uint;
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 pam_ffi {
    macro_rules! define {
        ($(#[$attr:meta])* $($name:ident = $value:expr),+) => {
            define!(
                @meta { $(#[$attr])* }
                $(pub const $name: u32 = $value;)+
            );
        };
        (@meta $m:tt $($i:item)+) => { define!(@expand $($m $i)+); };
        (@expand $({ $(#[$m:meta])* } $i:item)+) => {$($(#[$m])* $i)+};
    }
    const fn bit(n: u8) -> u32 {
        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.
    ///
    /// 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 = pam_ffi::PAM_SILENT as u32;

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

        // Flag used for `set_credentials`.

        /// Set user credentials for an authentication service.
        const ESTABLISH_CREDENTIALS = pam_ffi::PAM_ESTABLISH_CRED as u32;
        /// Delete user credentials associated with
        /// an authentication service.
        const DELETE_CREDENTIALS = pam_ffi::PAM_DELETE_CRED as u32;
        /// Reinitialize user credentials.
        const REINITIALIZE_CREDENTIALS = pam_ffi::PAM_REINITIALIZE_CRED as u32;
        /// Extend the lifetime of user credentials.
        const REFRESH_CREDENTIALS = pam_ffi::PAM_REFRESH_CRED as u32;

        // 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 = pam_ffi::PAM_CHANGE_EXPIRED_AUTHTOK as u32;
        /// 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 = pam_ffi::PAM_PRELIM_CHECK as u32;
        /// 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 = pam_ffi::PAM_UPDATE_AUTHTOK as u32;
    }
}

/// 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, 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,
    #[error("conversation function is event-driven and data is not available yet")]
    ConversationAgain = pam_ffi::PAM_CONV_AGAIN,
    #[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 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) => u32::from(otherwise) as i32,
        }
    }

    /// 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 as u32).try_into().unwrap_or(Self::SystemError)),
        }
    }
}

/// 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.
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_enums() {
        assert_eq!(Ok(()), ErrorCode::result_from(0));
        assert_eq!(
            pam_ffi::PAM_BAD_ITEM as i32,
            ErrorCode::result_to_c::<()>(Err(ErrorCode::BadItem))
        );
        assert_eq!(
            Err(ErrorCode::Abort),
            ErrorCode::result_from(pam_ffi::PAM_ABORT as i32)
        );
        assert_eq!(Err(ErrorCode::SystemError), ErrorCode::result_from(423));
    }
}