Mercurial > crates > nonstick
comparison src/constants.rs @ 171:e27c5c667a5a
Create full new types for return code and flags, separate end to end.
This plumbs the ReturnCode and RawFlags types through the places where
we call into or are called from PAM.
Also adds Sun documentation to the project.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Fri, 25 Jul 2025 20:52:14 -0400 |
parents | f052e2417195 |
children | 6727cbe56f4a |
comparison
equal
deleted
inserted
replaced
170:f052e2417195 | 171:e27c5c667a5a |
---|---|
1 //! Constants and enum values from the PAM library. | 1 //! Constants and enum values from the PAM library. |
2 | 2 |
3 use crate::_doc::{linklist, man7, manbsd, xsso}; | 3 use crate::_doc::{linklist, man7, manbsd, mansun, xsso}; |
4 use bitflags::bitflags; | 4 use bitflags::bitflags; |
5 use num_enum::{IntoPrimitive, TryFromPrimitive}; | |
6 use std::error::Error; | 5 use std::error::Error; |
7 use std::ffi::c_int; | 6 use std::ffi::c_int; |
8 use std::fmt; | 7 use std::fmt; |
9 use std::fmt::{Display, Formatter}; | 8 use std::fmt::{Display, Formatter}; |
10 use std::result::Result as StdResult; | 9 use std::result::Result as StdResult; |
11 | 10 |
12 #[cfg(features = "link")] | 11 macro_rules! wrapper { |
13 use libpam_sys_consts::constants as pam_constants; | 12 ( |
14 | 13 $(#[$m:meta])* |
15 | 14 $viz:vis $name:ident($wraps:ty); |
16 /// The union of constants available in all versions of PAM. | 15 ) => { |
17 /// The values here are fictitious and should not be used. | 16 $(#[$m])* |
18 #[cfg(not(features = "link"))] | 17 #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
19 mod pam_constants { | 18 #[repr(transparent)] |
20 pub const PAM_SUCCESS: i32 = 0; | 19 $viz struct $name(i32); |
21 | 20 |
22 /// Generates a sequence of values. | 21 impl From<i32> for $name { |
23 macro_rules! c_enum { | 22 fn from(value: i32) -> Self { |
24 ($first:ident = $value:expr, $($rest:ident,)*) => { | 23 Self(value) |
25 c_enum!(($value) $first, $($rest,)*); | 24 } |
26 }; | 25 } |
27 (($value:expr) $first:ident, $($rest:ident,)*) => { | 26 impl From<$name> for i32 { |
28 pub const $first: i32 = $value; | 27 fn from(value: $name) -> Self { |
29 c_enum!(($value+1) $($rest,)*); | 28 value.0 |
30 }; | 29 } |
31 (($value:expr)) => {}; | 30 } |
32 } | 31 } |
33 | 32 } |
34 // Since all these values are fictitious, we can start them wherever. | 33 |
35 // All the items. | 34 wrapper! { |
36 c_enum!( | 35 /// Type of the flags that PAM passes to us (or that we pass to PAM). |
37 PAM_SERVICE = 64, | 36 pub RawFlags(c_int); |
38 PAM_USER, | 37 } |
39 PAM_TTY, | 38 wrapper! { |
40 PAM_RHOST, | 39 /// The error code that we return to PAM. |
41 PAM_CONV, | 40 pub ReturnCode(c_int); |
42 PAM_AUTHTOK, | 41 } |
43 PAM_OLDAUTHTOK, | 42 |
44 PAM_RUSER, | 43 impl ReturnCode { |
45 PAM_USER_PROMPT, | 44 /// A successful return. |
46 // Linux-only items. | 45 pub const SUCCESS: Self = Self(0); |
47 PAM_FAIL_DELAY, | 46 } |
48 PAM_XDISPLAY, | 47 |
49 PAM_XAUTHDATA, | |
50 PAM_AUTHTOK_TYPE, | |
51 // OpenPAM-only items. | |
52 PAM_REPOSITORY, | |
53 PAM_AUTHTOK_PROMPT, | |
54 PAM_OLDAUTHTOK_PROMPT, | |
55 PAM_HOST, | |
56 // Sun-only items. | |
57 PAM_RESOURCE, | |
58 PAM_AUSER, | |
59 ); | |
60 | |
61 // Prompt types. | |
62 c_enum!( | |
63 PAM_PROMPT_ECHO_OFF = 96, | |
64 PAM_PROMPT_ECHO_ON, | |
65 PAM_ERROR_MSG, | |
66 PAM_TEXT_INFO, | |
67 PAM_RADIO_TYPE, | |
68 PAM_BINARY_PROMPT, | |
69 ); | |
70 | |
71 // Errors. | |
72 c_enum!( | |
73 PAM_OPEN_ERR = 128, | |
74 PAM_SYMBOL_ERR, | |
75 PAM_SERVICE_ERR, | |
76 PAM_SYSTEM_ERR, | |
77 PAM_BUF_ERR, | |
78 PAM_PERM_DENIED, | |
79 PAM_AUTH_ERR, | |
80 PAM_CRED_INSUFFICIENT, | |
81 PAM_AUTHINFO_UNAVAIL, | |
82 PAM_USER_UNKNOWN, | |
83 PAM_MAXTRIES, | |
84 PAM_NEW_AUTHTOK_REQD, | |
85 PAM_ACCT_EXPIRED, | |
86 PAM_SESSION_ERR, | |
87 PAM_CRED_UNAVAIL, | |
88 PAM_CRED_EXPIRED, | |
89 PAM_CRED_ERR, | |
90 PAM_NO_MODULE_DATA, | |
91 PAM_CONV_ERR, | |
92 PAM_AUTHTOK_ERR, | |
93 PAM_AUTHTOK_RECOVERY_ERR, | |
94 PAM_AUTHTOK_LOCK_BUSY, | |
95 PAM_AUTHTOK_DISABLE_AGING, | |
96 PAM_TRY_AGAIN, | |
97 PAM_IGNORE, | |
98 PAM_ABORT, | |
99 PAM_AUTHTOK_EXPIRED, | |
100 PAM_MODULE_UNKNOWN, | |
101 PAM_BAD_ITEM, | |
102 PAM_CONV_AGAIN, | |
103 PAM_INCOMPLETE, | |
104 // OpenPAM-only errors. | |
105 PAM_DOMAIN_UNKNOWN, | |
106 PAM_BAD_HANDLE, | |
107 PAM_BAD_FEATURE, | |
108 PAM_BAD_CONSTANT, | |
109 ); | |
110 | |
111 macro_rules! flag_enum { | |
112 ($first:ident = $value:expr, $($rest:ident,)*) => { | |
113 flag_enum!(($value) $first, $($rest,)*); | |
114 }; | |
115 (($value:expr) $first:ident, $($rest:ident,)*) => { | |
116 pub const $first: i32 = $value; | |
117 flag_enum!(($value*2) $($rest,)*); | |
118 }; | |
119 (($value:expr)) => {}; | |
120 } | |
121 | |
122 flag_enum!( | |
123 PAM_SILENT = 256, | |
124 PAM_DISALLOW_NULL_AUTHTOK, | |
125 PAM_ESTABLISH_CRED, | |
126 PAM_DELETE_CRED, | |
127 PAM_REINITIALIZE_CRED, | |
128 PAM_REFRESH_CRED, | |
129 | |
130 PAM_CHANGE_EXPIRED_AUTHTOK, | |
131 | |
132 PAM_PRELIM_CHECK, | |
133 PAM_UPDATE_AUTHTOK, | |
134 PAM_DATA_REPLACE, | |
135 PAM_DATA_SILENT, | |
136 ); | |
137 } | |
138 | |
139 /// Creates a bitflags! macro, with an extra SILENT element. | |
140 macro_rules! pam_flags { | 48 macro_rules! pam_flags { |
141 ( | 49 ( |
142 $(#[$m:meta])* | 50 $(#[$m:meta])* |
143 $name:ident { | 51 $name:ident { |
144 $($inner:tt)* | 52 $( |
53 $(#[$m_ident:ident $($m_arg:tt)*])* | |
54 const $item_name:ident = (link = $value_value:expr, else = $other_value:expr); | |
55 )* | |
145 } | 56 } |
146 ) => { | 57 ) => { |
147 bitflags! { | 58 bitflags! { |
59 #[derive(Clone, Copy, Debug, Default, PartialEq)] | |
148 $(#[$m])* | 60 $(#[$m])* |
149 #[derive(Clone, Copy, Debug, Default, PartialEq)] | 61 pub struct $name: u16 { |
150 #[repr(transparent)] | 62 $( |
151 pub struct $name: c_int { | 63 $(#[$m_ident $($m_arg)*])* |
152 /// The module should not generate any messages. | 64 const $item_name = $other_value; |
153 const SILENT = pam_constants::PAM_SILENT; | 65 )* |
154 $($inner)* | 66 } |
67 } | |
68 | |
69 #[cfg(feature = "link")] | |
70 impl From<RawFlags> for $name { | |
71 #[allow(unused_doc_comments)] | |
72 fn from(value: RawFlags) -> Self { | |
73 eprintln!(concat!(stringify!($name), " FROM RAW FLAGS")); | |
74 let value: c_int = value.into(); | |
75 let result = Self::empty(); | |
76 $( | |
77 $(#[$m_ident $($m_arg)*])* | |
78 let result = result | if value & $value_value == 0 { | |
79 eprintln!(concat!("checked against ", stringify!($value_value))); | |
80 Self::empty() | |
81 } else { | |
82 eprintln!(concat!("checked against ", stringify!($value_value), " success")); | |
83 Self::$item_name | |
84 }; | |
85 )* | |
86 result | |
87 } | |
88 } | |
89 | |
90 #[cfg(feature = "link")] | |
91 impl From<$name> for RawFlags { | |
92 #[allow(unused_doc_comments)] | |
93 fn from(value: $name) -> Self { | |
94 eprintln!(concat!("RAW FLAGS FROM ", stringify!($name))); | |
95 let result = 0; | |
96 $( | |
97 $(#[$m_ident $($m_arg)*])* | |
98 let result = result | if value.contains($name::$item_name) { | |
99 eprintln!(concat!("checked against ", stringify!($item_name), " success")); | |
100 $value_value | |
101 } else { | |
102 eprintln!(concat!("checked against ", stringify!($item_name))); | |
103 0 | |
104 }; | |
105 )* | |
106 Self(result) | |
155 } | 107 } |
156 } | 108 } |
157 } | 109 } |
158 } | 110 } |
159 | 111 |
160 pam_flags! { | 112 pam_flags! { |
161 /// Flags for authentication and account management. | 113 /// Flags for authentication and account management. |
162 AuthnFlags { | 114 AuthnFlags { |
115 /// The PAM module should not generate any messages. | |
116 const SILENT = (link = libpam_sys::PAM_SILENT, else = 0x8000); | |
117 | |
163 /// The module should return [AuthError](ErrorCode::AuthError) | 118 /// The module should return [AuthError](ErrorCode::AuthError) |
164 /// if the user has an empty authentication token, rather than | 119 /// if the user has an empty authentication token, rather than |
165 /// allowing them to log in. | 120 /// allowing them to log in. |
166 const DISALLOW_NULL_AUTHTOK = pam_constants::PAM_DISALLOW_NULL_AUTHTOK; | 121 const DISALLOW_NULL_AUTHTOK = (link = libpam_sys::PAM_DISALLOW_NULL_AUTHTOK, else = 0b1); |
167 } | 122 } |
168 } | 123 } |
169 | 124 |
170 pam_flags! { | 125 pam_flags! { |
171 /// Flags for changing the authentication token. | 126 /// Flags for changing the authentication token. |
172 AuthtokFlags { | 127 AuthtokFlags { |
128 /// The PAM module should not generate any messages. | |
129 const SILENT = (link = libpam_sys::PAM_SILENT, else = 0x8000); | |
130 | |
173 /// Indicates that the user's authentication token should | 131 /// Indicates that the user's authentication token should |
174 /// only be changed if it is expired. If not passed, | 132 /// only be changed if it is expired. If not passed, |
175 /// the authentication token should be changed unconditionally. | 133 /// the authentication token should be changed unconditionally. |
176 const CHANGE_EXPIRED_AUTHTOK = pam_constants::PAM_CHANGE_EXPIRED_AUTHTOK; | 134 const CHANGE_EXPIRED_AUTHTOK = (link = libpam_sys::PAM_CHANGE_EXPIRED_AUTHTOK, else = 0b10); |
177 | 135 |
178 /// Don't check if the password is any good (Sun only). | 136 /// Don't check if the password is any good (Sun only). |
179 #[cfg(pam_impl = "Sun")] | 137 #[cfg(pam_impl = "Sun")] |
180 const NO_AUTHTOK_CHECK = pam_constants::PAM_NO_AUTHTOK_CHECK; | 138 const NO_AUTHTOK_CHECK = (link = libpam_sys::PAM_NO_AUTHTOK_CHECK, else = 0b100); |
181 } | 139 } |
182 } | 140 } |
183 | 141 |
184 pam_flags! { | 142 pam_flags! { |
185 /// Common flag(s) shared by all PAM actions. | 143 /// Common flag(s) shared by all PAM actions. |
186 BaseFlags {} | 144 BaseFlags { |
145 /// The PAM module should not generate any messages. | |
146 const SILENT = (link = libpam_sys::PAM_SILENT, else = 0x8000); | |
147 } | |
187 } | 148 } |
188 | 149 |
189 #[cfg(feature = "openpam-ext")] | 150 #[cfg(feature = "openpam-ext")] |
190 const BAD_CONST: ErrorCode = ErrorCode::BadConstant; | 151 const BAD_CONST: ErrorCode = ErrorCode::BadConstant; |
191 #[cfg(not(feature = "openpam-ext"))] | 152 #[cfg(not(feature = "openpam-ext"))] |
195 ( | 156 ( |
196 $(#[$m:meta])* | 157 $(#[$m:meta])* |
197 $name:ident { | 158 $name:ident { |
198 $( | 159 $( |
199 $(#[$item_m:meta])* | 160 $(#[$item_m:meta])* |
200 $item_name:ident = $item_value:expr, | 161 $item_name:ident = $item_value:path, |
201 )* | 162 )* |
202 } | 163 } |
203 ) => { | 164 ) => { |
204 $(#[$m])* | 165 $(#[$m])* |
205 #[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive, IntoPrimitive)] | 166 #[derive(Clone, Copy, Debug, PartialEq)] |
206 #[repr(i32)] | |
207 pub enum $name { | 167 pub enum $name { |
208 $( | 168 $( |
209 $(#[$item_m])* | 169 $(#[$item_m])* |
210 $item_name = $item_value, | 170 $item_name, |
211 )* | 171 )* |
212 } | 172 } |
213 | 173 |
174 #[cfg(feature = "link")] | |
175 impl TryFrom<RawFlags> for $name { | |
176 type Error = ErrorCode; | |
177 fn try_from(value: RawFlags) -> Result<$name> { | |
178 match value.0 { | |
179 $( | |
180 $item_value => Ok(Self::$item_name), | |
181 )* | |
182 _ => Err(BAD_CONST), | |
183 } | |
184 } | |
185 } | |
186 | |
187 #[cfg(feature = "link")] | |
188 impl From<$name> for RawFlags { | |
189 fn from(value: $name) -> Self { | |
190 match value { | |
191 $( | |
192 $name::$item_name => $item_value.into(), | |
193 )* | |
194 } | |
195 } | |
196 } | |
197 | |
198 #[cfg(feature = "link")] | |
214 impl $name { | 199 impl $name { |
215 const ALL_VALUES: i32 = 0 $( | $item_value)*; | 200 const ALL_VALUES: i32 = 0 $( | $item_value)*; |
216 | 201 |
217 fn split(value: i32) -> Result<(Option<Self>, i32)> { | 202 fn split(value: RawFlags) -> Result<(Option<Self>, RawFlags)> { |
218 let me = value & Self::ALL_VALUES; | 203 let me = value.0 & Self::ALL_VALUES; |
219 let them = value & !Self::ALL_VALUES; | 204 let them = (value.0 & !Self::ALL_VALUES).into(); |
220 let me = match me { | 205 let me = match RawFlags(me) { |
221 0 => None, | 206 RawFlags(0) => None, |
222 n => Some(Self::try_from(n).map_err(|_| BAD_CONST)?), | 207 other => Some(Self::try_from(other).map_err(|_| BAD_CONST)?), |
223 }; | 208 }; |
224 Ok((me, them)) | 209 Ok((me, them)) |
225 } | 210 } |
226 } | 211 } |
227 } | 212 } |
229 | 214 |
230 flag_enum! { | 215 flag_enum! { |
231 /// The credential management action that should take place. | 216 /// The credential management action that should take place. |
232 CredAction { | 217 CredAction { |
233 /// Set the user's credentials from this module. Default if unspecified. | 218 /// Set the user's credentials from this module. Default if unspecified. |
234 Establish = pam_constants::PAM_ESTABLISH_CRED, | 219 Establish = libpam_sys::PAM_ESTABLISH_CRED, |
235 /// Revoke the user's credentials established by this module. | 220 /// Revoke the user's credentials established by this module. |
236 Delete = pam_constants::PAM_DELETE_CRED, | 221 Delete = libpam_sys::PAM_DELETE_CRED, |
237 /// Fully reinitialize the user's credentials from this module. | 222 /// Fully reinitialize the user's credentials from this module. |
238 Reinitialize = pam_constants::PAM_REINITIALIZE_CRED, | 223 Reinitialize = libpam_sys::PAM_REINITIALIZE_CRED, |
239 /// Extend the lifetime of the user's credentials from this module. | 224 /// Extend the lifetime of the user's credentials from this module. |
240 Refresh = pam_constants::PAM_REFRESH_CRED, | 225 Refresh = libpam_sys::PAM_REFRESH_CRED, |
241 } | 226 } |
242 } | 227 } |
243 | 228 |
229 #[cfg(feature = "link")] | |
244 impl CredAction { | 230 impl CredAction { |
245 /// Separates this enum from the remaining [`BaseFlags`]. | 231 /// Separates this enum from the remaining [`BaseFlags`]. |
246 pub fn extract(value: i32) -> Result<(Self, BaseFlags)> { | 232 pub(crate) fn extract(value: RawFlags) -> Result<(Self, BaseFlags)> { |
247 Self::split(value) | 233 Self::split(value).map(|(act, rest)| (act.unwrap_or_default(), BaseFlags::from(rest))) |
248 .map(|(act, rest)| (act.unwrap_or_default(), BaseFlags::from_bits_retain(rest))) | |
249 } | 234 } |
250 } | 235 } |
251 | 236 |
252 impl Default for CredAction { | 237 impl Default for CredAction { |
253 fn default() -> Self { | 238 fn default() -> Self { |
255 } | 240 } |
256 } | 241 } |
257 | 242 |
258 flag_enum! { | 243 flag_enum! { |
259 AuthtokAction { | 244 AuthtokAction { |
260 /// This is a preliminary call to check if we're ready to change passwords | 245 /// On this call, just validate that the password is acceptable |
261 /// and that the new password is acceptable. | 246 /// and that you have all the resources you need to change it. |
262 PreliminaryCheck = pam_constants::PAM_PRELIM_CHECK, | 247 /// |
263 /// You should actually update the password. | 248 /// This corresponds to the constant `PAM_PRELIM_CHECK`. |
264 Update = pam_constants::PAM_UPDATE_AUTHTOK, | 249 Validate = libpam_sys::PAM_PRELIM_CHECK, |
265 } | 250 /// Actually perform the update. |
266 } | 251 /// |
267 | 252 /// This corresponds to the constant `PAM_UPDATE_AUTHTOK`. |
253 Update = libpam_sys::PAM_UPDATE_AUTHTOK, | |
254 } | |
255 } | |
256 | |
257 #[cfg(feature = "link")] | |
268 impl AuthtokAction { | 258 impl AuthtokAction { |
269 /// Separates this enum from the remaining [`AuthtokFlags`]. | 259 /// Separates this enum from the remaining [`AuthtokFlags`]. |
270 pub fn extract(value: i32) -> Result<(Self, AuthtokFlags)> { | 260 pub(crate) fn extract(value: RawFlags) -> Result<(Self, AuthtokFlags)> { |
271 match Self::split(value)? { | 261 match Self::split(value)? { |
272 (Some(act), rest) => Ok((act, AuthtokFlags::from_bits_retain(rest))), | 262 (Some(act), rest) => Ok((act, AuthtokFlags::from(rest))), |
273 (None, _) => Err(BAD_CONST), | 263 (None, _) => Err(BAD_CONST), |
274 } | 264 } |
275 } | 265 } |
276 } | 266 } |
277 | 267 |
278 /// The PAM error return codes. | 268 /// Constructs an enum which has the values if it's linked |
279 /// | 269 macro_rules! linky_enum { |
280 /// These are returned by most PAM functions if an error of some kind occurs. | 270 ( |
281 /// | 271 $(#[$om:meta])* |
282 /// Instead of being an error code, success is represented by an Ok [`Result`]. | 272 pub enum $name:ident($wrap:ty) { |
283 /// | 273 $( |
284 /// # References | 274 $(#[$im:meta])* |
285 /// | 275 $key:ident = $value:path, |
286 #[doc = linklist!(pam: man7, manbsd)] | 276 )* |
287 /// - [X/SSO error code specification][xsso] | 277 } |
288 /// | 278 ) => { |
289 #[doc = man7!(3 pam "RETURN_VALUES")] | 279 $(#[$om])* |
290 #[doc = manbsd!(3 pam "RETURN%20VALUES")] | 280 #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
291 #[doc = xsso!("chap5.htm#tagcjh_06_02")] | 281 pub enum $name { |
292 #[allow(non_camel_case_types, dead_code)] | 282 $( |
293 #[derive(Copy, Clone, Debug, PartialEq, TryFromPrimitive, IntoPrimitive)] | 283 $(#[$im])* |
294 #[non_exhaustive] // C might give us anything! | 284 $key, |
295 #[repr(i32)] | 285 )* |
296 pub enum ErrorCode { | 286 } |
297 OpenError = pam_constants::PAM_OPEN_ERR, | 287 |
298 SymbolError = pam_constants::PAM_SYMBOL_ERR, | 288 #[cfg(feature = "link")] |
299 ServiceError = pam_constants::PAM_SERVICE_ERR, | 289 impl TryFrom<$wrap> for $name { |
300 SystemError = pam_constants::PAM_SYSTEM_ERR, | 290 type Error = ErrorCode; |
301 BufferError = pam_constants::PAM_BUF_ERR, | 291 fn try_from(value: $wrap) -> Result<Self> { |
302 PermissionDenied = pam_constants::PAM_PERM_DENIED, | 292 match value.into() { |
303 AuthenticationError = pam_constants::PAM_AUTH_ERR, | 293 $( |
304 CredentialsInsufficient = pam_constants::PAM_CRED_INSUFFICIENT, | 294 $(#[$im])* |
305 AuthInfoUnavailable = pam_constants::PAM_AUTHINFO_UNAVAIL, | 295 $value => Ok(Self::$key), |
306 UserUnknown = pam_constants::PAM_USER_UNKNOWN, | 296 )* |
307 MaxTries = pam_constants::PAM_MAXTRIES, | 297 _ => Err(BAD_CONST), |
308 NewAuthTokRequired = pam_constants::PAM_NEW_AUTHTOK_REQD, | 298 } |
309 AccountExpired = pam_constants::PAM_ACCT_EXPIRED, | 299 } |
310 SessionError = pam_constants::PAM_SESSION_ERR, | 300 } |
311 CredentialsUnavailable = pam_constants::PAM_CRED_UNAVAIL, | 301 |
312 CredentialsExpired = pam_constants::PAM_CRED_EXPIRED, | 302 #[cfg(feature = "link")] |
313 CredentialsError = pam_constants::PAM_CRED_ERR, | 303 impl From<$name> for $wrap { |
314 NoModuleData = pam_constants::PAM_NO_MODULE_DATA, | 304 fn from(value: $name) -> Self { |
315 ConversationError = pam_constants::PAM_CONV_ERR, | 305 match value { |
316 AuthTokError = pam_constants::PAM_AUTHTOK_ERR, | 306 $( |
317 AuthTokRecoveryError = pam_constants::PAM_AUTHTOK_RECOVERY_ERR, | 307 $(#[$im])* |
318 AuthTokLockBusy = pam_constants::PAM_AUTHTOK_LOCK_BUSY, | 308 $name::$key => $value.into(), |
319 AuthTokDisableAging = pam_constants::PAM_AUTHTOK_DISABLE_AGING, | 309 )* |
320 TryAgain = pam_constants::PAM_TRY_AGAIN, | 310 } |
321 Ignore = pam_constants::PAM_IGNORE, | 311 } |
322 Abort = pam_constants::PAM_ABORT, | 312 } |
323 AuthTokExpired = pam_constants::PAM_AUTHTOK_EXPIRED, | 313 } |
324 #[cfg(feature = "basic-ext")] | 314 } |
325 ModuleUnknown = pam_constants::PAM_MODULE_UNKNOWN, | 315 |
326 #[cfg(feature = "basic-ext")] | 316 linky_enum! { |
327 BadItem = pam_constants::PAM_BAD_ITEM, | 317 /// The PAM error return codes. |
328 #[cfg(feature = "linux-pam-ext")] | 318 /// |
329 ConversationAgain = pam_constants::PAM_CONV_AGAIN, | 319 /// These are returned by most PAM functions if an error of some kind occurs. |
330 #[cfg(feature = "linux-pam-ext")] | 320 /// |
331 Incomplete = pam_constants::PAM_INCOMPLETE, | 321 /// Instead of being an error code, success is represented by an Ok [`Result`]. |
332 #[cfg(feature = "openpam-ext")] | 322 /// |
333 DomainUnknown = pam_constants::PAM_DOMAIN_UNKNOWN, | 323 /// **Do not depend upon the numerical value of these error codes, |
334 #[cfg(feature = "openpam-ext")] | 324 /// or the enum's representation type. |
335 BadHandle = pam_constants::PAM_BAD_HANDLE, | 325 /// The available codes and their values will vary depending upon |
336 #[cfg(feature = "openpam-ext")] | 326 /// PAM implementation.** |
337 BadFeature = pam_constants::PAM_BAD_FEATURE, | 327 /// |
338 #[cfg(feature = "openpam-ext")] | 328 /// # References |
339 BadConstant = pam_constants::PAM_BAD_CONSTANT, | 329 /// |
330 #[doc = linklist!(pam: man7, manbsd, mansun)] | |
331 /// - [X/SSO error code specification][xsso] | |
332 /// | |
333 #[doc = man7!(3 pam "RETURN_VALUES")] | |
334 #[doc = manbsd!(3 pam "RETURN%20VALUES")] | |
335 #[doc = mansun!([3 "pam"] pam "return-values")] | |
336 #[doc = xsso!("chap5.htm#tagcjh_06_02")] | |
337 #[allow(non_camel_case_types, dead_code)] | |
338 #[non_exhaustive] // Different PAMs have different error code sets. | |
339 pub enum ErrorCode(ReturnCode) { | |
340 OpenError = libpam_sys::PAM_OPEN_ERR, | |
341 SymbolError = libpam_sys::PAM_SYMBOL_ERR, | |
342 ServiceError = libpam_sys::PAM_SERVICE_ERR, | |
343 SystemError = libpam_sys::PAM_SYSTEM_ERR, | |
344 BufferError = libpam_sys::PAM_BUF_ERR, | |
345 PermissionDenied = libpam_sys::PAM_PERM_DENIED, | |
346 AuthenticationError = libpam_sys::PAM_AUTH_ERR, | |
347 CredentialsInsufficient = libpam_sys::PAM_CRED_INSUFFICIENT, | |
348 AuthInfoUnavailable = libpam_sys::PAM_AUTHINFO_UNAVAIL, | |
349 UserUnknown = libpam_sys::PAM_USER_UNKNOWN, | |
350 MaxTries = libpam_sys::PAM_MAXTRIES, | |
351 NewAuthTokRequired = libpam_sys::PAM_NEW_AUTHTOK_REQD, | |
352 AccountExpired = libpam_sys::PAM_ACCT_EXPIRED, | |
353 SessionError = libpam_sys::PAM_SESSION_ERR, | |
354 CredentialsUnavailable = libpam_sys::PAM_CRED_UNAVAIL, | |
355 CredentialsExpired = libpam_sys::PAM_CRED_EXPIRED, | |
356 CredentialsError = libpam_sys::PAM_CRED_ERR, | |
357 NoModuleData = libpam_sys::PAM_NO_MODULE_DATA, | |
358 ConversationError = libpam_sys::PAM_CONV_ERR, | |
359 AuthTokError = libpam_sys::PAM_AUTHTOK_ERR, | |
360 AuthTokRecoveryError = libpam_sys::PAM_AUTHTOK_RECOVERY_ERR, | |
361 AuthTokLockBusy = libpam_sys::PAM_AUTHTOK_LOCK_BUSY, | |
362 AuthTokDisableAging = libpam_sys::PAM_AUTHTOK_DISABLE_AGING, | |
363 TryAgain = libpam_sys::PAM_TRY_AGAIN, | |
364 Ignore = libpam_sys::PAM_IGNORE, | |
365 Abort = libpam_sys::PAM_ABORT, | |
366 AuthTokExpired = libpam_sys::PAM_AUTHTOK_EXPIRED, | |
367 #[cfg(feature = "basic-ext")] | |
368 ModuleUnknown = libpam_sys::PAM_MODULE_UNKNOWN, | |
369 #[cfg(feature = "basic-ext")] | |
370 BadItem = libpam_sys::PAM_BAD_ITEM, | |
371 #[cfg(feature = "linux-pam-ext")] | |
372 ConversationAgain = libpam_sys::PAM_CONV_AGAIN, | |
373 #[cfg(feature = "linux-pam-ext")] | |
374 Incomplete = libpam_sys::PAM_INCOMPLETE, | |
375 #[cfg(feature = "openpam-ext")] | |
376 DomainUnknown = libpam_sys::PAM_DOMAIN_UNKNOWN, | |
377 #[cfg(feature = "openpam-ext")] | |
378 BadHandle = libpam_sys::PAM_BAD_HANDLE, | |
379 #[cfg(feature = "openpam-ext")] | |
380 BadFeature = libpam_sys::PAM_BAD_FEATURE, | |
381 #[cfg(feature = "openpam-ext")] | |
382 BadConstant = libpam_sys::PAM_BAD_CONSTANT, | |
383 } | |
340 } | 384 } |
341 | 385 |
342 /// A PAM-specific Result type with an [ErrorCode] error. | 386 /// A PAM-specific Result type with an [ErrorCode] error. |
343 pub type Result<T> = StdResult<T, ErrorCode>; | 387 pub type Result<T> = StdResult<T, ErrorCode>; |
344 | 388 |
345 impl Display for ErrorCode { | 389 impl Display for ErrorCode { |
390 #[cfg(all( | |
391 feature = "link", | |
392 any(pam_impl = "LinuxPam", pam_impl = "OpenPam", pam_impl = "Sun") | |
393 ))] | |
346 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | 394 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
347 match strerror((*self).into()) { | 395 use std::ffi::CStr; |
348 Some(err) => f.write_str(err), | 396 use std::ptr; |
349 None => self.fmt_internal(f), | 397 // SAFETY: PAM impls don't care about the PAM handle and always return |
350 } | 398 // static strings. |
399 let got = unsafe { libpam_sys::pam_strerror(ptr::null(), *self as c_int) }; | |
400 if got.is_null() { | |
401 // This shouldn't happen. | |
402 write!(f, "PAM error: {self:?} ({:?})", *self as c_int) | |
403 } else { | |
404 // SAFETY: We just got this back from PAM and we checked if it's null. | |
405 f.write_str(&unsafe { CStr::from_ptr(got) }.to_string_lossy()) | |
406 } | |
407 } | |
408 #[cfg(not(all( | |
409 feature = "link", | |
410 any(pam_impl = "LinuxPam", pam_impl = "OpenPam", pam_impl = "Sun") | |
411 )))] | |
412 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | |
413 fmt::Debug::fmt(self, f) | |
351 } | 414 } |
352 } | 415 } |
353 | 416 |
354 impl Error for ErrorCode {} | 417 impl Error for ErrorCode {} |
355 | 418 |
419 #[cfg(feature = "link")] | |
356 impl ErrorCode { | 420 impl ErrorCode { |
357 /// Converts this [Result] into a C-compatible result code. | 421 pub(crate) fn result_from(ret: c_int) -> Result<()> { |
358 pub fn result_to_c<T>(value: Result<T>) -> c_int { | 422 match ret { |
423 0 => Ok(()), | |
424 value => Err(ReturnCode(value).try_into().unwrap_or(BAD_CONST)), | |
425 } | |
426 } | |
427 } | |
428 | |
429 impl<T> From<Result<T>> for ReturnCode { | |
430 fn from(value: Result<T>) -> Self { | |
359 match value { | 431 match value { |
360 Ok(_) => 0, // PAM_SUCCESS | 432 Ok(_) => ReturnCode::SUCCESS, |
361 Err(otherwise) => otherwise.into(), | 433 Err(otherwise) => otherwise.into() |
362 } | 434 } |
363 } | 435 } |
364 | 436 } |
365 /// Converts a C result code into a [Result], with success as Ok. | 437 |
366 /// Invalid values are returned as a [Self::SystemError]. | 438 #[cfg(all(test, feature = "link"))] |
367 pub fn result_from(value: c_int) -> Result<()> { | |
368 match value { | |
369 0 => Ok(()), | |
370 value => Err(value.try_into().unwrap_or(Self::SystemError)), | |
371 } | |
372 } | |
373 | |
374 /// A basic Display implementation for if we don't link against PAM. | |
375 fn fmt_internal(self, f: &mut Formatter<'_>) -> fmt::Result { | |
376 let n: c_int = self.into(); | |
377 write!(f, "PAM error: {self:?} ({n})") | |
378 } | |
379 } | |
380 | |
381 /// Gets a string version of an error message. | |
382 #[cfg(feature = "link")] | |
383 pub fn strerror(code: c_int) -> Option<&'static str> { | |
384 use std::ffi::CStr; | |
385 use std::ptr; | |
386 // SAFETY: PAM impls don't care about the PAM handle and always return | |
387 // static strings. | |
388 let strerror = unsafe { libpam_sys::pam_strerror(ptr::null(), code as c_int) }; | |
389 // SAFETY: We just got this back from PAM and we checked if it's null. | |
390 (!strerror.is_null()) | |
391 .then(|| unsafe { CStr::from_ptr(strerror) }.to_str().ok()) | |
392 .flatten() | |
393 } | |
394 | |
395 /// Dummy implementation of strerror so that it always returns None. | |
396 #[cfg(not(feature = "link"))] | |
397 pub fn strerror(_: c_int) -> Option<&'static str> { | |
398 None | |
399 } | |
400 | |
401 #[cfg(test)] | |
402 mod tests { | 439 mod tests { |
403 use super::*; | 440 use super::*; |
404 | 441 |
405 #[test] | 442 #[test] |
406 fn test_enums() { | 443 fn test_enums() { |
407 assert_eq!(Ok(()), ErrorCode::result_from(0)); | 444 assert_eq!(Ok(()), ErrorCode::result_from(0)); |
408 assert_eq!( | 445 assert_eq!( |
409 pam_constants::PAM_SESSION_ERR, | 446 ReturnCode(libpam_sys::PAM_SESSION_ERR), |
410 ErrorCode::result_to_c::<()>(Err(ErrorCode::SessionError)) | 447 Result::<()>::Err(ErrorCode::SessionError).into() |
411 ); | 448 ); |
412 assert_eq!( | 449 assert_eq!( |
413 Err(ErrorCode::Abort), | 450 Result::<()>::Err(ErrorCode::Abort), |
414 ErrorCode::result_from(pam_constants::PAM_ABORT) | 451 ErrorCode::result_from(libpam_sys::PAM_ABORT) |
415 ); | 452 ); |
416 assert_eq!(Err(ErrorCode::SystemError), ErrorCode::result_from(423)); | 453 assert_eq!(Err(BAD_CONST), ErrorCode::result_from(423)); |
454 } | |
455 | |
456 #[test] | |
457 fn test_flags() { | |
458 assert_eq!( | |
459 AuthtokFlags::CHANGE_EXPIRED_AUTHTOK | AuthtokFlags::SILENT, | |
460 AuthtokFlags::from(RawFlags( | |
461 libpam_sys::PAM_SILENT | libpam_sys::PAM_CHANGE_EXPIRED_AUTHTOK | |
462 )) | |
463 ); | |
464 assert_eq!( | |
465 RawFlags(libpam_sys::PAM_DISALLOW_NULL_AUTHTOK), | |
466 AuthnFlags::DISALLOW_NULL_AUTHTOK.into() | |
467 ); | |
468 assert_eq!( | |
469 RawFlags(libpam_sys::PAM_SILENT | libpam_sys::PAM_CHANGE_EXPIRED_AUTHTOK), | |
470 (AuthtokFlags::SILENT | AuthtokFlags::CHANGE_EXPIRED_AUTHTOK).into() | |
471 ); | |
472 } | |
473 | |
474 #[test] | |
475 #[cfg(pam_impl = "Sun")] | |
476 fn test_flags_sun() { | |
477 assert_eq!( | |
478 AuthtokFlags::NO_AUTHTOK_CHECK, | |
479 AuthtokFlags::from(RawFlags(libpam_sys::PAM_NO_AUTHTOK_CHECK)) | |
480 ); | |
481 assert_eq!( | |
482 RawFlags( | |
483 libpam_sys::PAM_SILENT | |
484 | libpam_sys::PAM_CHANGE_EXPIRED_AUTHTOK | |
485 | libpam_sys::PAM_NO_AUTHTOK_CHECK | |
486 ), | |
487 (AuthtokFlags::SILENT | |
488 | AuthtokFlags::CHANGE_EXPIRED_AUTHTOK | |
489 | AuthtokFlags::NO_AUTHTOK_CHECK) | |
490 .into() | |
491 ); | |
417 } | 492 } |
418 | 493 |
419 #[test] | 494 #[test] |
420 fn test_flag_enums() { | 495 fn test_flag_enums() { |
421 AuthtokAction::extract(-1).expect_err("too many set"); | 496 AuthtokAction::extract((-1).into()).expect_err("too many set"); |
422 AuthtokAction::extract(0).expect_err("too few set"); | 497 AuthtokAction::extract(0.into()).expect_err("too few set"); |
423 assert_eq!( | 498 assert_eq!( |
424 Ok(( | 499 Ok((AuthtokAction::Update, AuthtokFlags::SILENT,)), |
425 AuthtokAction::Update, | 500 AuthtokAction::extract( |
426 AuthtokFlags::from_bits_retain(0x7f000000) | 501 (libpam_sys::PAM_SILENT | libpam_sys::PAM_UPDATE_AUTHTOK).into() |
427 )), | 502 ) |
428 AuthtokAction::extract(0x7f000000 | pam_constants::PAM_UPDATE_AUTHTOK) | 503 ); |
429 ); | 504 CredAction::extract(0xffff.into()).expect_err("too many set"); |
430 CredAction::extract(0xffff).expect_err("too many set"); | |
431 assert_eq!( | 505 assert_eq!( |
432 Ok((CredAction::Establish, BaseFlags::empty())), | 506 Ok((CredAction::Establish, BaseFlags::empty())), |
433 CredAction::extract(0) | 507 CredAction::extract(0.into()) |
434 ); | 508 ); |
435 assert_eq!( | 509 assert_eq!( |
436 Ok((CredAction::Delete, BaseFlags::from_bits_retain(0x55000000))), | 510 Ok((CredAction::Delete, BaseFlags::SILENT)), |
437 CredAction::extract(0x55000000 | pam_constants::PAM_DELETE_CRED) | 511 CredAction::extract((libpam_sys::PAM_SILENT | libpam_sys::PAM_DELETE_CRED).into()) |
438 ); | 512 ); |
439 } | 513 } |
440 } | 514 } |