Mercurial > crates > nonstick
view src/constants.rs @ 170:f052e2417195
Completely avoid using libpam_sys if we're not actually linking.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Wed, 16 Jul 2025 18:45:20 -0400 |
parents | 2f5913131295 |
children | e27c5c667a5a |
line wrap: on
line source
//! Constants and enum values from the PAM library. use crate::_doc::{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; #[cfg(features = "link")] use libpam_sys_consts::constants as pam_constants; /// The union of constants available in all versions of PAM. /// The values here are fictitious and should not be used. #[cfg(not(features = "link"))] mod pam_constants { pub const PAM_SUCCESS: i32 = 0; /// Generates a sequence of values. macro_rules! c_enum { ($first:ident = $value:expr, $($rest:ident,)*) => { c_enum!(($value) $first, $($rest,)*); }; (($value:expr) $first:ident, $($rest:ident,)*) => { pub const $first: i32 = $value; c_enum!(($value+1) $($rest,)*); }; (($value:expr)) => {}; } // Since all these values are fictitious, we can start them wherever. // All the items. c_enum!( PAM_SERVICE = 64, PAM_USER, PAM_TTY, PAM_RHOST, PAM_CONV, PAM_AUTHTOK, PAM_OLDAUTHTOK, PAM_RUSER, PAM_USER_PROMPT, // Linux-only items. PAM_FAIL_DELAY, PAM_XDISPLAY, PAM_XAUTHDATA, PAM_AUTHTOK_TYPE, // OpenPAM-only items. PAM_REPOSITORY, PAM_AUTHTOK_PROMPT, PAM_OLDAUTHTOK_PROMPT, PAM_HOST, // Sun-only items. PAM_RESOURCE, PAM_AUSER, ); // Prompt types. c_enum!( PAM_PROMPT_ECHO_OFF = 96, PAM_PROMPT_ECHO_ON, PAM_ERROR_MSG, PAM_TEXT_INFO, PAM_RADIO_TYPE, PAM_BINARY_PROMPT, ); // Errors. c_enum!( PAM_OPEN_ERR = 128, PAM_SYMBOL_ERR, PAM_SERVICE_ERR, PAM_SYSTEM_ERR, PAM_BUF_ERR, PAM_PERM_DENIED, PAM_AUTH_ERR, PAM_CRED_INSUFFICIENT, PAM_AUTHINFO_UNAVAIL, PAM_USER_UNKNOWN, PAM_MAXTRIES, PAM_NEW_AUTHTOK_REQD, PAM_ACCT_EXPIRED, PAM_SESSION_ERR, PAM_CRED_UNAVAIL, PAM_CRED_EXPIRED, PAM_CRED_ERR, PAM_NO_MODULE_DATA, PAM_CONV_ERR, PAM_AUTHTOK_ERR, PAM_AUTHTOK_RECOVERY_ERR, PAM_AUTHTOK_LOCK_BUSY, PAM_AUTHTOK_DISABLE_AGING, PAM_TRY_AGAIN, PAM_IGNORE, PAM_ABORT, PAM_AUTHTOK_EXPIRED, PAM_MODULE_UNKNOWN, PAM_BAD_ITEM, PAM_CONV_AGAIN, PAM_INCOMPLETE, // OpenPAM-only errors. PAM_DOMAIN_UNKNOWN, PAM_BAD_HANDLE, PAM_BAD_FEATURE, PAM_BAD_CONSTANT, ); macro_rules! flag_enum { ($first:ident = $value:expr, $($rest:ident,)*) => { flag_enum!(($value) $first, $($rest,)*); }; (($value:expr) $first:ident, $($rest:ident,)*) => { pub const $first: i32 = $value; flag_enum!(($value*2) $($rest,)*); }; (($value:expr)) => {}; } flag_enum!( PAM_SILENT = 256, PAM_DISALLOW_NULL_AUTHTOK, PAM_ESTABLISH_CRED, PAM_DELETE_CRED, PAM_REINITIALIZE_CRED, PAM_REFRESH_CRED, PAM_CHANGE_EXPIRED_AUTHTOK, PAM_PRELIM_CHECK, PAM_UPDATE_AUTHTOK, PAM_DATA_REPLACE, PAM_DATA_SILENT, ); } /// Creates a bitflags! macro, with an extra SILENT element. macro_rules! pam_flags { ( $(#[$m:meta])* $name:ident { $($inner:tt)* } ) => { bitflags! { $(#[$m])* #[derive(Clone, Copy, Debug, Default, PartialEq)] #[repr(transparent)] pub struct $name: c_int { /// The module should not generate any messages. const SILENT = pam_constants::PAM_SILENT; $($inner)* } } } } pam_flags! { /// Flags for authentication and account management. AuthnFlags { /// The module should return [AuthError](ErrorCode::AuthError) /// if the user has an empty authentication token, rather than /// allowing them to log in. const DISALLOW_NULL_AUTHTOK = pam_constants::PAM_DISALLOW_NULL_AUTHTOK; } } pam_flags! { /// Flags for changing the authentication token. AuthtokFlags { /// Indicates that the user's authentication token should /// only be changed if it is expired. If not passed, /// the authentication token should be changed unconditionally. const CHANGE_EXPIRED_AUTHTOK = pam_constants::PAM_CHANGE_EXPIRED_AUTHTOK; /// Don't check if the password is any good (Sun only). #[cfg(pam_impl = "Sun")] const NO_AUTHTOK_CHECK = pam_constants::PAM_NO_AUTHTOK_CHECK; } } pam_flags! { /// Common flag(s) shared by all PAM actions. BaseFlags {} } #[cfg(feature = "openpam-ext")] const BAD_CONST: ErrorCode = ErrorCode::BadConstant; #[cfg(not(feature = "openpam-ext"))] const BAD_CONST: ErrorCode = ErrorCode::SystemError; macro_rules! flag_enum { ( $(#[$m:meta])* $name:ident { $( $(#[$item_m:meta])* $item_name:ident = $item_value:expr, )* } ) => { $(#[$m])* #[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive, IntoPrimitive)] #[repr(i32)] pub enum $name { $( $(#[$item_m])* $item_name = $item_value, )* } impl $name { const ALL_VALUES: i32 = 0 $( | $item_value)*; fn split(value: i32) -> Result<(Option<Self>, i32)> { let me = value & Self::ALL_VALUES; let them = value & !Self::ALL_VALUES; let me = match me { 0 => None, n => Some(Self::try_from(n).map_err(|_| BAD_CONST)?), }; Ok((me, them)) } } } } flag_enum! { /// The credential management action that should take place. CredAction { /// Set the user's credentials from this module. Default if unspecified. Establish = pam_constants::PAM_ESTABLISH_CRED, /// Revoke the user's credentials established by this module. Delete = pam_constants::PAM_DELETE_CRED, /// Fully reinitialize the user's credentials from this module. Reinitialize = pam_constants::PAM_REINITIALIZE_CRED, /// Extend the lifetime of the user's credentials from this module. Refresh = pam_constants::PAM_REFRESH_CRED, } } impl CredAction { /// Separates this enum from the remaining [`BaseFlags`]. pub fn extract(value: i32) -> Result<(Self, BaseFlags)> { Self::split(value) .map(|(act, rest)| (act.unwrap_or_default(), BaseFlags::from_bits_retain(rest))) } } impl Default for CredAction { fn default() -> Self { Self::Establish } } flag_enum! { AuthtokAction { /// This is a preliminary call to check if we're ready to change passwords /// and that the new password is acceptable. PreliminaryCheck = pam_constants::PAM_PRELIM_CHECK, /// You should actually update the password. Update = pam_constants::PAM_UPDATE_AUTHTOK, } } impl AuthtokAction { /// Separates this enum from the remaining [`AuthtokFlags`]. pub fn extract(value: i32) -> Result<(Self, AuthtokFlags)> { match Self::split(value)? { (Some(act), rest) => Ok((act, AuthtokFlags::from_bits_retain(rest))), (None, _) => Err(BAD_CONST), } } } /// 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_constants::PAM_OPEN_ERR, SymbolError = pam_constants::PAM_SYMBOL_ERR, ServiceError = pam_constants::PAM_SERVICE_ERR, SystemError = pam_constants::PAM_SYSTEM_ERR, BufferError = pam_constants::PAM_BUF_ERR, PermissionDenied = pam_constants::PAM_PERM_DENIED, AuthenticationError = pam_constants::PAM_AUTH_ERR, CredentialsInsufficient = pam_constants::PAM_CRED_INSUFFICIENT, AuthInfoUnavailable = pam_constants::PAM_AUTHINFO_UNAVAIL, UserUnknown = pam_constants::PAM_USER_UNKNOWN, MaxTries = pam_constants::PAM_MAXTRIES, NewAuthTokRequired = pam_constants::PAM_NEW_AUTHTOK_REQD, AccountExpired = pam_constants::PAM_ACCT_EXPIRED, SessionError = pam_constants::PAM_SESSION_ERR, CredentialsUnavailable = pam_constants::PAM_CRED_UNAVAIL, CredentialsExpired = pam_constants::PAM_CRED_EXPIRED, CredentialsError = pam_constants::PAM_CRED_ERR, NoModuleData = pam_constants::PAM_NO_MODULE_DATA, ConversationError = pam_constants::PAM_CONV_ERR, AuthTokError = pam_constants::PAM_AUTHTOK_ERR, AuthTokRecoveryError = pam_constants::PAM_AUTHTOK_RECOVERY_ERR, AuthTokLockBusy = pam_constants::PAM_AUTHTOK_LOCK_BUSY, AuthTokDisableAging = pam_constants::PAM_AUTHTOK_DISABLE_AGING, TryAgain = pam_constants::PAM_TRY_AGAIN, Ignore = pam_constants::PAM_IGNORE, Abort = pam_constants::PAM_ABORT, AuthTokExpired = pam_constants::PAM_AUTHTOK_EXPIRED, #[cfg(feature = "basic-ext")] ModuleUnknown = pam_constants::PAM_MODULE_UNKNOWN, #[cfg(feature = "basic-ext")] BadItem = pam_constants::PAM_BAD_ITEM, #[cfg(feature = "linux-pam-ext")] ConversationAgain = pam_constants::PAM_CONV_AGAIN, #[cfg(feature = "linux-pam-ext")] Incomplete = pam_constants::PAM_INCOMPLETE, #[cfg(feature = "openpam-ext")] DomainUnknown = pam_constants::PAM_DOMAIN_UNKNOWN, #[cfg(feature = "openpam-ext")] BadHandle = pam_constants::PAM_BAD_HANDLE, #[cfg(feature = "openpam-ext")] BadFeature = pam_constants::PAM_BAD_FEATURE, #[cfg(feature = "openpam-ext")] BadConstant = pam_constants::PAM_BAD_CONSTANT, } /// 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_constants::PAM_SESSION_ERR, ErrorCode::result_to_c::<()>(Err(ErrorCode::SessionError)) ); assert_eq!( Err(ErrorCode::Abort), ErrorCode::result_from(pam_constants::PAM_ABORT) ); assert_eq!(Err(ErrorCode::SystemError), ErrorCode::result_from(423)); } #[test] fn test_flag_enums() { AuthtokAction::extract(-1).expect_err("too many set"); AuthtokAction::extract(0).expect_err("too few set"); assert_eq!( Ok(( AuthtokAction::Update, AuthtokFlags::from_bits_retain(0x7f000000) )), AuthtokAction::extract(0x7f000000 | pam_constants::PAM_UPDATE_AUTHTOK) ); CredAction::extract(0xffff).expect_err("too many set"); assert_eq!( Ok((CredAction::Establish, BaseFlags::empty())), CredAction::extract(0) ); assert_eq!( Ok((CredAction::Delete, BaseFlags::from_bits_retain(0x55000000))), CredAction::extract(0x55000000 | pam_constants::PAM_DELETE_CRED) ); } }