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 }