Mercurial > crates > nonstick
view src/constants.rs @ 166:2f5913131295
Separate flag/action flags into flags and action.
This also individualizes the type of flag for each PAM function,
so that you can only call a function with the right flags and values.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Tue, 15 Jul 2025 00:32:24 -0400 |
parents | 4b3a5095f68c |
children | f052e2417195 |
line wrap: on
line source
//! Constants and enum values from the PAM library. use crate::_doc::{linklist, man7, manbsd, xsso}; use bitflags::bitflags; use libpam_sys_consts::constants; 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; /// 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 = 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 = 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 = constants::PAM_CHANGE_EXPIRED_AUTHTOK; /// Don't check if the password is any good (Sun only). #[cfg(pam_impl = "Sun")] const NO_AUTHTOK_CHECK = 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 = constants::PAM_ESTABLISH_CRED, /// Revoke the user's credentials established by this module. Delete = constants::PAM_DELETE_CRED, /// Fully reinitialize the user's credentials from this module. Reinitialize = constants::PAM_REINITIALIZE_CRED, /// Extend the lifetime of the user's credentials from this module. Refresh = 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 = constants::PAM_PRELIM_CHECK, /// You should actually update the password. Update = 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 = constants::PAM_OPEN_ERR, SymbolError = constants::PAM_SYMBOL_ERR, ServiceError = constants::PAM_SERVICE_ERR, SystemError = constants::PAM_SYSTEM_ERR, BufferError = constants::PAM_BUF_ERR, PermissionDenied = constants::PAM_PERM_DENIED, AuthenticationError = constants::PAM_AUTH_ERR, CredentialsInsufficient = constants::PAM_CRED_INSUFFICIENT, AuthInfoUnavailable = constants::PAM_AUTHINFO_UNAVAIL, UserUnknown = constants::PAM_USER_UNKNOWN, MaxTries = constants::PAM_MAXTRIES, NewAuthTokRequired = constants::PAM_NEW_AUTHTOK_REQD, AccountExpired = constants::PAM_ACCT_EXPIRED, SessionError = constants::PAM_SESSION_ERR, CredentialsUnavailable = constants::PAM_CRED_UNAVAIL, CredentialsExpired = constants::PAM_CRED_EXPIRED, CredentialsError = constants::PAM_CRED_ERR, NoModuleData = constants::PAM_NO_MODULE_DATA, ConversationError = constants::PAM_CONV_ERR, AuthTokError = constants::PAM_AUTHTOK_ERR, AuthTokRecoveryError = constants::PAM_AUTHTOK_RECOVERY_ERR, AuthTokLockBusy = constants::PAM_AUTHTOK_LOCK_BUSY, AuthTokDisableAging = constants::PAM_AUTHTOK_DISABLE_AGING, TryAgain = constants::PAM_TRY_AGAIN, Ignore = constants::PAM_IGNORE, Abort = constants::PAM_ABORT, AuthTokExpired = constants::PAM_AUTHTOK_EXPIRED, #[cfg(feature = "basic-ext")] ModuleUnknown = constants::PAM_MODULE_UNKNOWN, #[cfg(feature = "basic-ext")] BadItem = constants::PAM_BAD_ITEM, #[cfg(feature = "linux-pam-ext")] ConversationAgain = constants::PAM_CONV_AGAIN, #[cfg(feature = "linux-pam-ext")] Incomplete = constants::PAM_INCOMPLETE, #[cfg(feature = "openpam-ext")] DomainUnknown = constants::PAM_DOMAIN_UNKNOWN, #[cfg(feature = "openpam-ext")] BadHandle = constants::PAM_BAD_HANDLE, #[cfg(feature = "openpam-ext")] BadFeature = constants::PAM_BAD_FEATURE, #[cfg(feature = "openpam-ext")] BadConstant = 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!( constants::PAM_SESSION_ERR, ErrorCode::result_to_c::<()>(Err(ErrorCode::SessionError)) ); assert_eq!( Err(ErrorCode::Abort), ErrorCode::result_from(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(0x7fff0000) )), AuthtokAction::extract(0x7fff0000 | 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 | constants::PAM_DELETE_CRED) ); } }