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 }