view src/constants.rs @ 132:0b6a17f8c894 default tip

Get constant test working again with OpenPAM.
author Paul Fisher <paul@pfish.zone>
date Wed, 02 Jul 2025 02:34:29 -0400
parents a632a8874131
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)]

use crate::{linklist, man7, manbsd, xsso};
use bitflags::bitflags;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use std::error::Error;
use std::ffi::c_int;
use std::fmt;
use std::fmt::{Display, Formatter};
use std::result::Result as StdResult;

/// Values for constants not provided by certain PAM implementations.
///
/// **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.**
mod pam_ffi {
    pub use libpam_sys::*;

    macro_rules! define {
        ($(#[$attr:meta])* $($name:ident = $value:expr;)+) => {
            define!(
                @meta { $(#[$attr])* }
                $(pub const $name: i32 = $value;)+
            );
        };
        (@meta $m:tt $($i:item)+) => { define!(@expand $($m $i)+); };
        (@expand $({ $(#[$m:meta])* } $i:item)+) => {$($(#[$m])* $i)+};
    }

    define!(
        /// A fictitious constant for testing purposes.
        #[cfg(not(feature = "link"))]
        #[cfg_pam_impl(not("OpenPam"))]
        PAM_BAD_CONSTANT = 513;
        PAM_BAD_FEATURE = 514;
    );

    define!(
        /// A fictitious constant for testing purposes.
        #[cfg(not(feature = "link"))]
        #[cfg_pam_impl(not(any("LinuxPam", "OpenPam")))]
        PAM_BAD_ITEM = 515;
        PAM_MODULE_UNKNOWN = 516;
    );

    define!(
        /// A fictitious constant for testing purposes.
        #[cfg(not(feature = "link"))]
        #[cfg_pam_impl(not("LinuxPam"))]
        PAM_CONV_AGAIN = 517;
        PAM_INCOMPLETE = 518;
    );
}

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_int {
        /// The module should not generate any messages.
        const SILENT = libpam_sys::PAM_SILENT;

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

        // Flag used for `set_credentials`.

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

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

/// The PAM error return codes.
///
/// These are returned by most PAM functions if an error of some kind occurs.
///
/// Instead of being an error code, success is represented by an Ok [`Result`].
///
/// # References
///
#[doc = linklist!(pam: man7, manbsd)]
/// - [X/SSO error code specification][xsso]
///
#[doc = man7!(3 pam "RETURN_VALUES")]
#[doc = manbsd!(3 pam "RETURN%20VALUES")]
#[doc = xsso!("chap5.htm#tagcjh_06_02")]
#[allow(non_camel_case_types, dead_code)]
#[derive(Copy, Clone, Debug, PartialEq, TryFromPrimitive, IntoPrimitive)]
#[non_exhaustive] // C might give us anything!
#[repr(i32)]
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,
    #[cfg(feature = "basic-ext")]
    ModuleUnknown = pam_ffi::PAM_MODULE_UNKNOWN,
    #[cfg(feature = "basic-ext")]
    BadItem = pam_ffi::PAM_BAD_ITEM,
    #[cfg(feature = "linux-pam-ext")]
    ConversationAgain = pam_ffi::PAM_CONV_AGAIN,
    #[cfg(feature = "linux-pam-ext")]
    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 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) => 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)),
        }
    }

    /// A basic Display implementation for if we don't link against PAM.
    fn fmt_internal(self, f: &mut Formatter<'_>) -> fmt::Result {
        let n: c_int = self.into();
        write!(f, "PAM error: {self:?} ({n})")
    }
}

/// Gets a string version of an error message.
#[cfg(feature = "link")]
pub fn strerror(code: c_int) -> Option<&'static str> {
    use std::ffi::CStr;
    use std::ptr;
    // SAFETY: PAM impls don't care about the PAM handle and always return
    // static strings.
    let strerror = unsafe { libpam_sys::pam_strerror(ptr::null(), code as c_int) };
    // SAFETY: We just got this back from PAM and we checked if it's null.
    (!strerror.is_null())
        .then(|| unsafe { CStr::from_ptr(strerror) }.to_str().ok())
        .flatten()
}

/// Dummy implementation of strerror so that it always returns None.
#[cfg(not(feature = "link"))]
pub fn strerror(_: c_int) -> Option<&'static str> {
    None
}

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

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