view src/constants.rs @ 102:94eb11cb1798 default tip

Improve documentation for pam_start.
author Paul Fisher <paul@pfish.zone>
date Tue, 24 Jun 2025 18:11:38 -0400
parents efe2f5f8b5b2
children
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));
    }
}