view src/constants.rs @ 100:3f11b8d30f63

Implement environment variable management. This actually wires up the environment variable handling to libpam, so that applications and modules can manage the environment through the authentication process.
author Paul Fisher <paul@pfish.zone>
date Tue, 24 Jun 2025 17:08:01 -0400
parents efe2f5f8b5b2
children dfcd96a74ac4
line wrap: on
line source

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

// We have a lot of dumb casts that we just gotta do because of differences
// between Linux-PAM and OpenPAM header files.
#![allow(clippy::unnecessary_cast)]

#[cfg(feature = "link")]
use crate::libpam::pam_ffi;
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.
///
/// **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 {
    use std::ffi::c_uint;

    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
    );

    /// Dummy implementation of strerror so that it always returns None.
    pub fn strerror(val: c_uint) -> Option<&'static str> {
        _ = val;
        None
    }
}

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, Default, 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, TryFromPrimitive, IntoPrimitive)]
#[non_exhaustive] // C might give us anything!
#[repr(u32)]
pub enum ErrorCode {
    OpenError = pam_ffi::PAM_OPEN_ERR,
    SymbolError = pam_ffi::PAM_SYMBOL_ERR,
    ServiceError = pam_ffi::PAM_SERVICE_ERR,
    SystemError = pam_ffi::PAM_SYSTEM_ERR,
    BufferError = pam_ffi::PAM_BUF_ERR,
    PermissionDenied = pam_ffi::PAM_PERM_DENIED,
    AuthenticationError = pam_ffi::PAM_AUTH_ERR,
    CredentialsInsufficient = pam_ffi::PAM_CRED_INSUFFICIENT,
    AuthInfoUnavailable = pam_ffi::PAM_AUTHINFO_UNAVAIL,
    UserUnknown = pam_ffi::PAM_USER_UNKNOWN,
    MaxTries = pam_ffi::PAM_MAXTRIES,
    NewAuthTokRequired = pam_ffi::PAM_NEW_AUTHTOK_REQD,
    AccountExpired = pam_ffi::PAM_ACCT_EXPIRED,
    SessionError = pam_ffi::PAM_SESSION_ERR,
    CredentialsUnavailable = pam_ffi::PAM_CRED_UNAVAIL,
    CredentialsExpired = pam_ffi::PAM_CRED_EXPIRED,
    CredentialsError = pam_ffi::PAM_CRED_ERR,
    NoModuleData = pam_ffi::PAM_NO_MODULE_DATA,
    ConversationError = pam_ffi::PAM_CONV_ERR,
    AuthTokError = pam_ffi::PAM_AUTHTOK_ERR,
    AuthTokRecoveryError = pam_ffi::PAM_AUTHTOK_RECOVERY_ERR,
    AuthTokLockBusy = pam_ffi::PAM_AUTHTOK_LOCK_BUSY,
    AuthTokDisableAging = pam_ffi::PAM_AUTHTOK_DISABLE_AGING,
    TryAgain = pam_ffi::PAM_TRY_AGAIN,
    Ignore = pam_ffi::PAM_IGNORE,
    Abort = pam_ffi::PAM_ABORT,
    AuthTokExpired = pam_ffi::PAM_AUTHTOK_EXPIRED,
    ModuleUnknown = pam_ffi::PAM_MODULE_UNKNOWN,
    BadItem = pam_ffi::PAM_BAD_ITEM,
    #[cfg(feature = "linux-pam-extensions")]
    ConversationAgain = pam_ffi::PAM_CONV_AGAIN,
    #[cfg(feature = "linux-pam-extensions")]
    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 {
        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)),
        }
    }

    /// 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.
/// 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));
    }
}