Mercurial > crates > nonstick
comparison src/constants.rs @ 80:5aa1a010f1e8
Start using PAM headers; improve owned/borrowed distinction.
- Uses bindgen to generate bindings (only if needed).
- Gets the story together on owned vs. borrowed handles.
- Reduces number of mutable borrows in handle operation
(since `PamHandle` is neither `Send` nor `Sync`,
we never have to worry about thread safety.
- Improves a bunch of macros so we don't have our own
special syntax for docs.
- Implement question indirection for standard XSSO PAM implementations.
| author | Paul Fisher <paul@pfish.zone> |
|---|---|
| date | Tue, 10 Jun 2025 01:09:30 -0400 |
| parents | 351bdc13005e |
| children | a638a45e5f1f |
comparison
equal
deleted
inserted
replaced
| 79:2128123b9406 | 80:5aa1a010f1e8 |
|---|---|
| 1 //! Constants and enum values from the PAM library. | 1 //! Constants and enum values from the PAM library. |
| 2 | 2 |
| 3 #[cfg(feature = "link")] | |
| 4 use crate::libpam::pam_ffi; | |
| 3 use bitflags::bitflags; | 5 use bitflags::bitflags; |
| 4 use libc::{c_int, c_uint}; | 6 use libc::c_int; |
| 5 use num_derive::FromPrimitive; | 7 use num_enum::{IntoPrimitive, TryFromPrimitive}; |
| 6 use num_traits::FromPrimitive; | |
| 7 use std::any; | |
| 8 use std::marker::PhantomData; | |
| 9 use std::result::Result as StdResult; | 8 use std::result::Result as StdResult; |
| 9 | |
| 10 /// Arbitrary values for PAM constants when not linking against system PAM. | |
| 11 /// | |
| 12 /// **The values of these constants are deliberately selected _not_ to match | |
| 13 /// any PAM implementations. Applications should always use the symbolic value | |
| 14 /// and not a magic number.** | |
| 15 #[cfg(not(feature = "link"))] | |
| 16 mod ffi { | |
| 17 macro_rules! define { | |
| 18 ($(#[$attr:meta])* $($name:ident = $value:expr),+) => { | |
| 19 define!( | |
| 20 @meta { $(#[$attr])* } | |
| 21 $(pub const $name: i32 = $value;)+ | |
| 22 ); | |
| 23 }; | |
| 24 (@meta $m:tt $($i:item)+) => { define!(@expand $($m $i)+); }; | |
| 25 (@expand $({ $(#[$m:meta])* } $i:item)+) => {$($(#[$m])* $i)+}; | |
| 26 } | |
| 27 const fn bit(n: i8) -> i32 { | |
| 28 1 << n | |
| 29 } | |
| 30 define!( | |
| 31 PAM_SILENT = bit(13), | |
| 32 PAM_DISALLOW_NULL_AUTHTOK = bit(14), | |
| 33 PAM_ESTABLISH_CRED = bit(15), | |
| 34 PAM_DELETE_CRED = bit(16), | |
| 35 PAM_REINITIALIZE_CRED = bit(17), | |
| 36 PAM_REFRESH_CRED = bit(18), | |
| 37 PAM_CHANGE_EXPIRED_AUTHTOK = bit(19), | |
| 38 PAM_PRELIM_CHECK = bit(20), | |
| 39 PAM_UPDATE_AUTHTOK = bit(21) | |
| 40 ); | |
| 41 | |
| 42 define!( | |
| 43 PAM_ABORT = 513, | |
| 44 PAM_ACCT_EXPIRED = 514, | |
| 45 PAM_AUTHINFO_UNAVAIL = 515, | |
| 46 PAM_AUTHTOK_DISABLE_AGING = 516, | |
| 47 PAM_AUTHTOK_ERR = 517, | |
| 48 PAM_AUTHTOK_EXPIRED = 518, | |
| 49 PAM_AUTHTOK_LOCK_BUSY = 519, | |
| 50 PAM_AUTHTOK_RECOVERY_ERR = 520, | |
| 51 PAM_AUTH_ERR = 521, | |
| 52 PAM_BAD_ITEM = 522, | |
| 53 PAM_BUF_ERR = 533, | |
| 54 PAM_CONV_AGAIN = 534, | |
| 55 PAM_CONV_ERR = 535, | |
| 56 PAM_CRED_ERR = 536, | |
| 57 PAM_CRED_EXPIRED = 537, | |
| 58 PAM_CRED_INSUFFICIENT = 538, | |
| 59 PAM_CRED_UNAVAIL = 539, | |
| 60 PAM_IGNORE = 540, | |
| 61 PAM_INCOMPLETE = 541, | |
| 62 PAM_MAXTRIES = 542, | |
| 63 PAM_MODULE_UNKNOWN = 543, | |
| 64 PAM_NEW_AUTHTOK_REQD = 544, | |
| 65 PAM_NO_MODULE_DATA = 545, | |
| 66 PAM_OPEN_ERR = 546, | |
| 67 PAM_PERM_DENIED = 547, | |
| 68 PAM_SERVICE_ERR = 548, | |
| 69 PAM_SESSION_ERR = 549, | |
| 70 PAM_SYMBOL_ERR = 550, | |
| 71 PAM_SYSTEM_ERR = 551, | |
| 72 PAM_TRY_AGAIN = 552, | |
| 73 PAM_USER_UNKNOWN = 553 | |
| 74 ); | |
| 75 } | |
| 10 | 76 |
| 11 bitflags! { | 77 bitflags! { |
| 12 /// The available PAM flags. | 78 /// The available PAM flags. |
| 13 /// | 79 /// |
| 14 /// See `/usr/include/security/_pam_types.h` and | 80 /// See `/usr/include/security/_pam_types.h` and |
| 15 /// See `/usr/include/security/pam_modules.h` for more details. | 81 /// See `/usr/include/security/pam_modules.h` for more details. |
| 16 #[derive(Debug, PartialEq)] | 82 #[derive(Debug, PartialEq)] |
| 17 #[repr(transparent)] | 83 #[repr(transparent)] |
| 18 pub struct Flags: c_uint { | 84 pub struct Flags: c_int { |
| 19 /// The module should not generate any messages. | 85 /// The module should not generate any messages. |
| 20 const SILENT = 0x8000; | 86 const SILENT = pam_ffi::PAM_SILENT; |
| 21 | 87 |
| 22 /// The module should return [ErrorCode::AuthError] | 88 /// The module should return [ErrorCode::AuthError] |
| 23 /// if the user has an empty authentication token | 89 /// if the user has an empty authentication token |
| 24 /// rather than immediately accepting them. | 90 /// rather than immediately accepting them. |
| 25 const DISALLOW_NULL_AUTHTOK = 0x0001; | 91 const DISALLOW_NULL_AUTHTOK = pam_ffi::PAM_DISALLOW_NULL_AUTHTOK; |
| 26 | 92 |
| 27 // Flag used for `set_credentials`. | 93 // Flag used for `set_credentials`. |
| 28 | 94 |
| 29 /// Set user credentials for an authentication service. | 95 /// Set user credentials for an authentication service. |
| 30 const ESTABLISH_CREDENTIALS = 0x0002; | 96 const ESTABLISH_CREDENTIALS = pam_ffi::PAM_ESTABLISH_CRED; |
| 31 /// Delete user credentials associated with | 97 /// Delete user credentials associated with |
| 32 /// an authentication service. | 98 /// an authentication service. |
| 33 const DELETE_CREDENTIALS = 0x0004; | 99 const DELETE_CREDENTIALS = pam_ffi::PAM_DELETE_CRED; |
| 34 /// Reinitialize user credentials. | 100 /// Reinitialize user credentials. |
| 35 const REINITIALIZE_CREDENTIALS = 0x0008; | 101 const REINITIALIZE_CREDENTIALS = pam_ffi::PAM_REINITIALIZE_CRED; |
| 36 /// Extend the lifetime of user credentials. | 102 /// Extend the lifetime of user credentials. |
| 37 const REFRESH_CREDENTIALS = 0x0010; | 103 const REFRESH_CREDENTIALS = pam_ffi::PAM_REFRESH_CRED; |
| 38 | 104 |
| 39 // Flags used for password changing. | 105 // Flags used for password changing. |
| 40 | 106 |
| 41 /// The password service should only update those passwords | 107 /// The password service should only update those passwords |
| 42 /// that have aged. If this flag is _not_ passed, | 108 /// that have aged. If this flag is _not_ passed, |
| 43 /// the password service should update all passwords. | 109 /// the password service should update all passwords. |
| 44 /// | 110 /// |
| 45 /// This flag is only used by `change_authtok`. | 111 /// This flag is only used by `change_authtok`. |
| 46 const CHANGE_EXPIRED_AUTHTOK = 0x0020; | 112 const CHANGE_EXPIRED_AUTHTOK = pam_ffi::PAM_CHANGE_EXPIRED_AUTHTOK; |
| 47 | 113 |
| 48 /// This is a preliminary check for password changing. | 114 /// This is a preliminary check for password changing. |
| 49 /// The password should not be changed. | 115 /// The password should not be changed. |
| 50 /// | 116 /// |
| 51 /// This is only used between PAM and a module. | 117 /// This is only used between PAM and a module. |
| 52 /// Applications may not use this flag. | 118 /// Applications may not use this flag. |
| 53 /// | 119 /// |
| 54 /// This flag is only used by `change_authtok`. | 120 /// This flag is only used by `change_authtok`. |
| 55 const PRELIMINARY_CHECK = 0x4000; | 121 const PRELIMINARY_CHECK = pam_ffi::PAM_PRELIM_CHECK; |
| 56 /// The password should actuallyPR be updated. | 122 /// The password should actuallyPR be updated. |
| 57 /// This and [Self::PRELIMINARY_CHECK] are mutually exclusive. | 123 /// This and [Self::PRELIMINARY_CHECK] are mutually exclusive. |
| 58 /// | 124 /// |
| 59 /// This is only used between PAM and a module. | 125 /// This is only used between PAM and a module. |
| 60 /// Applications may not use this flag. | 126 /// Applications may not use this flag. |
| 61 /// | 127 /// |
| 62 /// This flag is only used by `change_authtok`. | 128 /// This flag is only used by `change_authtok`. |
| 63 const UPDATE_AUTHTOK = 0x2000; | 129 const UPDATE_AUTHTOK = pam_ffi::PAM_UPDATE_AUTHTOK; |
| 64 } | 130 } |
| 65 } | 131 } |
| 66 | 132 |
| 67 /// The Linux-PAM error return values. Success is an Ok [Result]. | 133 /// The Linux-PAM error return values. Success is an Ok [Result]. |
| 68 /// | 134 /// |
| 69 /// Most abbreviations (except `AuthTok` and `Max`) are now full words. | 135 /// Most abbreviations (except `AuthTok` and `Max`) are now full words. |
| 70 /// For more detailed information, see | 136 /// For more detailed information, see |
| 71 /// `/usr/include/security/_pam_types.h`. | 137 /// `/usr/include/security/_pam_types.h`. |
| 72 #[allow(non_camel_case_types, dead_code)] | 138 #[allow(non_camel_case_types, dead_code)] |
| 73 #[derive(Copy, Clone, Debug, PartialEq, thiserror::Error, FromPrimitive)] | 139 #[derive(Copy, Clone, Debug, PartialEq, thiserror::Error, TryFromPrimitive, IntoPrimitive)] |
| 74 #[non_exhaustive] // C might give us anything! | 140 #[non_exhaustive] // C might give us anything! |
| 141 #[repr(i32)] | |
| 75 pub enum ErrorCode { | 142 pub enum ErrorCode { |
| 76 #[error("dlopen() failure when dynamically loading a service module")] | 143 #[error("dlopen() failure when dynamically loading a service module")] |
| 77 OpenError = 1, | 144 OpenError = pam_ffi::PAM_OPEN_ERR, |
| 78 #[error("symbol not found")] | 145 #[error("symbol not found")] |
| 79 SymbolError = 2, | 146 SymbolError = pam_ffi::PAM_SYMBOL_ERR, |
| 80 #[error("error in service module")] | 147 #[error("error in service module")] |
| 81 ServiceError = 3, | 148 ServiceError = pam_ffi::PAM_SERVICE_ERR, |
| 82 #[error("system error")] | 149 #[error("system error")] |
| 83 SystemError = 4, | 150 SystemError = pam_ffi::PAM_SYSTEM_ERR, |
| 84 #[error("memory buffer error")] | 151 #[error("memory buffer error")] |
| 85 BufferError = 5, | 152 BufferError = pam_ffi::PAM_BUF_ERR, |
| 86 #[error("permission denied")] | 153 #[error("permission denied")] |
| 87 PermissionDenied = 6, | 154 PermissionDenied = pam_ffi::PAM_PERM_DENIED, |
| 88 #[error("authentication failure")] | 155 #[error("authentication failure")] |
| 89 AuthenticationError = 7, | 156 AuthenticationError = pam_ffi::PAM_AUTH_ERR, |
| 90 #[error("cannot access authentication data due to insufficient credentials")] | 157 #[error("cannot access authentication data due to insufficient credentials")] |
| 91 CredentialsInsufficient = 8, | 158 CredentialsInsufficient = pam_ffi::PAM_CRED_INSUFFICIENT, |
| 92 #[error("underlying authentication service cannot retrieve authentication information")] | 159 #[error("underlying authentication service cannot retrieve authentication information")] |
| 93 AuthInfoUnavailable = 9, | 160 AuthInfoUnavailable = pam_ffi::PAM_AUTHINFO_UNAVAIL, |
| 94 #[error("user not known to the underlying authentication module")] | 161 #[error("user not known to the underlying authentication module")] |
| 95 UserUnknown = 10, | 162 UserUnknown = pam_ffi::PAM_USER_UNKNOWN, |
| 96 #[error("retry limit reached; do not attempt further")] | 163 #[error("retry limit reached; do not attempt further")] |
| 97 MaxTries = 11, | 164 MaxTries = pam_ffi::PAM_MAXTRIES, |
| 98 #[error("new authentication token required")] | 165 #[error("new authentication token required")] |
| 99 NewAuthTokRequired = 12, | 166 NewAuthTokRequired = pam_ffi::PAM_NEW_AUTHTOK_REQD, |
| 100 #[error("user account has expired")] | 167 #[error("user account has expired")] |
| 101 AccountExpired = 13, | 168 AccountExpired = pam_ffi::PAM_ACCT_EXPIRED, |
| 102 #[error("cannot make/remove an entry for the specified session")] | 169 #[error("cannot make/remove an entry for the specified session")] |
| 103 SessionError = 14, | 170 SessionError = pam_ffi::PAM_SESSION_ERR, |
| 104 #[error("underlying authentication service cannot retrieve user credentials")] | 171 #[error("underlying authentication service cannot retrieve user credentials")] |
| 105 CredentialsUnavailable = 15, | 172 CredentialsUnavailable = pam_ffi::PAM_CRED_UNAVAIL, |
| 106 #[error("user credentials expired")] | 173 #[error("user credentials expired")] |
| 107 CredentialsExpired = 16, | 174 CredentialsExpired = pam_ffi::PAM_CRED_EXPIRED, |
| 108 #[error("failure setting user credentials")] | 175 #[error("failure setting user credentials")] |
| 109 CredentialsError = 17, | 176 CredentialsError = pam_ffi::PAM_CRED_ERR, |
| 110 #[error("no module-specific data is present")] | 177 #[error("no module-specific data is present")] |
| 111 NoModuleData = 18, | 178 NoModuleData = pam_ffi::PAM_NO_MODULE_DATA, |
| 112 #[error("conversation error")] | 179 #[error("conversation error")] |
| 113 ConversationError = 19, | 180 ConversationError = pam_ffi::PAM_CONV_ERR, |
| 114 #[error("authentication token manipulation error")] | 181 #[error("authentication token manipulation error")] |
| 115 AuthTokError = 20, | 182 AuthTokError = pam_ffi::PAM_AUTHTOK_ERR, |
| 116 #[error("authentication information cannot be recovered")] | 183 #[error("authentication information cannot be recovered")] |
| 117 AuthTokRecoveryError = 21, | 184 AuthTokRecoveryError = pam_ffi::PAM_AUTHTOK_RECOVERY_ERR, |
| 118 #[error("authentication token lock busy")] | 185 #[error("authentication token lock busy")] |
| 119 AuthTokLockBusy = 22, | 186 AuthTokLockBusy = pam_ffi::PAM_AUTHTOK_LOCK_BUSY, |
| 120 #[error("authentication token aging disabled")] | 187 #[error("authentication token aging disabled")] |
| 121 AuthTokDisableAging = 23, | 188 AuthTokDisableAging = pam_ffi::PAM_AUTHTOK_DISABLE_AGING, |
| 122 #[error("preliminary password check failed")] | 189 #[error("preliminary password check failed")] |
| 123 TryAgain = 24, | 190 TryAgain = pam_ffi::PAM_TRY_AGAIN, |
| 124 #[error("ignore underlying account module, regardless of control flag")] | 191 #[error("ignore underlying account module, regardless of control flag")] |
| 125 Ignore = 25, | 192 Ignore = pam_ffi::PAM_IGNORE, |
| 126 #[error("critical error; this module should fail now")] | 193 #[error("critical error; this module should fail now")] |
| 127 Abort = 26, | 194 Abort = pam_ffi::PAM_ABORT, |
| 128 #[error("authentication token has expired")] | 195 #[error("authentication token has expired")] |
| 129 AuthTokExpired = 27, | 196 AuthTokExpired = pam_ffi::PAM_AUTHTOK_EXPIRED, |
| 130 #[error("module is not known")] | 197 #[error("module is not known")] |
| 131 ModuleUnknown = 28, | 198 ModuleUnknown = pam_ffi::PAM_MODULE_UNKNOWN, |
| 132 #[error("bad item passed to pam_[whatever]_item")] | 199 #[error("bad item passed to pam_[whatever]_item")] |
| 133 BadItem = 29, | 200 BadItem = pam_ffi::PAM_BAD_ITEM, |
| 134 #[error("conversation function is event-driven and data is not available yet")] | 201 #[error("conversation function is event-driven and data is not available yet")] |
| 135 ConversationAgain = 30, | 202 ConversationAgain = pam_ffi::PAM_CONV_AGAIN, |
| 136 #[error("call this function again to complete authentication stack")] | 203 #[error("call this function again to complete authentication stack")] |
| 137 Incomplete = 31, | 204 Incomplete = pam_ffi::PAM_INCOMPLETE, |
| 138 } | 205 } |
| 139 | 206 |
| 140 /// A PAM-specific Result type with an [ErrorCode] error. | 207 /// A PAM-specific Result type with an [ErrorCode] error. |
| 141 pub type Result<T> = StdResult<T, ErrorCode>; | 208 pub type Result<T> = StdResult<T, ErrorCode>; |
| 142 | 209 |
| 157 value => Err(value.try_into().unwrap_or(Self::SystemError)), | 224 value => Err(value.try_into().unwrap_or(Self::SystemError)), |
| 158 } | 225 } |
| 159 } | 226 } |
| 160 } | 227 } |
| 161 | 228 |
| 162 impl TryFrom<c_int> for ErrorCode { | |
| 163 type Error = InvalidEnum<Self>; | |
| 164 | |
| 165 fn try_from(value: c_int) -> StdResult<Self, Self::Error> { | |
| 166 Self::from_i32(value).ok_or(value.into()) | |
| 167 } | |
| 168 } | |
| 169 | |
| 170 impl From<ErrorCode> for c_int { | |
| 171 fn from(val: ErrorCode) -> Self { | |
| 172 val as Self | |
| 173 } | |
| 174 } | |
| 175 | |
| 176 /// Error returned when attempting to coerce an invalid C integer into an enum. | |
| 177 #[derive(Debug, PartialEq, thiserror::Error)] | |
| 178 #[error("{0} is not a valid {type}", type = any::type_name::<T>())] | |
| 179 pub struct InvalidEnum<T>(c_int, PhantomData<T>); | |
| 180 | |
| 181 impl<T> From<InvalidEnum<T>> for c_int { | |
| 182 fn from(value: InvalidEnum<T>) -> Self { | |
| 183 value.0 | |
| 184 } | |
| 185 } | |
| 186 | |
| 187 impl<T> From<c_int> for InvalidEnum<T> { | |
| 188 fn from(value: c_int) -> Self { | |
| 189 Self(value, PhantomData) | |
| 190 } | |
| 191 } | |
| 192 | |
| 193 /// Returned when text that should not have any `\0` bytes in it does. | 229 /// Returned when text that should not have any `\0` bytes in it does. |
| 194 /// Analogous to [`std::ffi::NulError`], but the data it was created from | 230 /// Analogous to [`std::ffi::NulError`], but the data it was created from |
| 195 /// is borrowed. | 231 /// is borrowed. |
| 196 #[cfg(test)] | 232 #[cfg(test)] |
| 197 mod tests { | 233 mod tests { |
| 198 use super::*; | 234 use super::*; |
| 199 | 235 |
| 200 #[test] | 236 #[test] |
| 201 fn test_enums() { | 237 fn test_enums() { |
| 202 assert_eq!(Ok(ErrorCode::ServiceError), 3.try_into()); | |
| 203 assert_eq!(Err(InvalidEnum::from(999)), ErrorCode::try_from(999)); | |
| 204 assert_eq!(Ok(()), ErrorCode::result_from(0)); | 238 assert_eq!(Ok(()), ErrorCode::result_from(0)); |
| 205 assert_eq!(Err(ErrorCode::Abort), ErrorCode::result_from(26)); | 239 assert_eq!( |
| 240 pam_ffi::PAM_BAD_ITEM, | |
| 241 ErrorCode::result_to_c::<()>(Err(ErrorCode::BadItem)) | |
| 242 ); | |
| 243 assert_eq!( | |
| 244 Err(ErrorCode::Abort), | |
| 245 ErrorCode::result_from(pam_ffi::PAM_ABORT) | |
| 246 ); | |
| 206 assert_eq!(Err(ErrorCode::SystemError), ErrorCode::result_from(423)); | 247 assert_eq!(Err(ErrorCode::SystemError), ErrorCode::result_from(423)); |
| 207 assert!(InvalidEnum::<ErrorCode>(33, PhantomData) | 248 } |
| 208 .to_string() | 249 } |
| 209 .starts_with("33 is not a valid ")); | |
| 210 } | |
| 211 } |
