Mercurial > crates > nonstick
comparison src/module.rs @ 56:daa2cde64601
Big big refactor. Probably should have been multiple changes.
- Makes FFI safer by explicitly specifying c_int in calls.
- Uses ToPrimitive/FromPrimitive to make this easier.
- Pulls PamFlag variables into a bitflags! struct.
- Pulls PamMessageStyle variables into an enum.
- Renames ResultCode to ErrorCode.
- Switches from PAM_SUCCESS to using a Result<(), ErrorCode>.
- Uses thiserror to make ErrorCode into an Error.
- Gets rid of pam_try! because now we have Results.
- Expands some names (e.g. Conv to Conversation).
- Adds more doc comments.
- Returns passwords as a SecureString, to avoid unnecessarily
keeping it around in memory.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sun, 04 May 2025 02:56:55 -0400 |
parents | 9d1160b02d2c |
children | 3f4a77aa88be |
comparison
equal
deleted
inserted
replaced
55:676675c3d434 | 56:daa2cde64601 |
---|---|
1 //! Functions for use in pam modules. | 1 //! Functions for use in pam modules. |
2 | 2 |
3 use crate::constants::{PamFlag, PamResultCode}; | 3 use crate::constants::{Flags, PamResult, ErrorCode}; |
4 use crate::items::{Item, ItemType}; | 4 use crate::items::{Item, ItemType}; |
5 use libc::c_char; | 5 use libc::c_char; |
6 use std::ffi::{CStr, CString}; | 6 use std::ffi::{c_int, CStr, CString}; |
7 use secure_string::SecureString; | |
7 | 8 |
8 /// Opaque type, used as a pointer when making pam API calls. | 9 /// Opaque type, used as a pointer when making pam API calls. |
9 /// | 10 /// |
10 /// A module is invoked via an external function such as `pam_sm_authenticate`. | 11 /// A module is invoked via an external function such as `pam_sm_authenticate`. |
11 /// Such a call provides a pam handle pointer. The same pointer should be given | 12 /// Such a call provides a pam handle pointer. The same pointer should be given |
19 extern "C" { | 20 extern "C" { |
20 fn pam_get_data( | 21 fn pam_get_data( |
21 pamh: *const PamHandle, | 22 pamh: *const PamHandle, |
22 module_data_name: *const c_char, | 23 module_data_name: *const c_char, |
23 data: &mut *const libc::c_void, | 24 data: &mut *const libc::c_void, |
24 ) -> PamResultCode; | 25 ) -> c_int; |
25 | 26 |
26 fn pam_set_data( | 27 fn pam_set_data( |
27 pamh: *const PamHandle, | 28 pamh: *const PamHandle, |
28 module_data_name: *const c_char, | 29 module_data_name: *const c_char, |
29 data: *mut libc::c_void, | 30 data: *mut libc::c_void, |
30 cleanup: extern "C" fn( | 31 cleanup: extern "C" fn( |
31 pamh: *const PamHandle, | 32 pamh: *const PamHandle, |
32 data: *mut libc::c_void, | 33 data: *mut libc::c_void, |
33 error_status: PamResultCode, | 34 error_status: c_int, |
34 ), | 35 ), |
35 ) -> PamResultCode; | 36 ) -> c_int; |
36 | 37 |
37 fn pam_get_item( | 38 fn pam_get_item( |
38 pamh: *const PamHandle, | 39 pamh: *const PamHandle, |
39 item_type: ItemType, | 40 item_type: c_int, |
40 item: &mut *const libc::c_void, | 41 item: &mut *const libc::c_void, |
41 ) -> PamResultCode; | 42 ) -> c_int; |
42 | 43 |
43 fn pam_set_item( | 44 fn pam_set_item(pamh: *mut PamHandle, item_type: c_int, item: *const libc::c_void) -> c_int; |
44 pamh: *mut PamHandle, | 45 |
45 item_type: ItemType, | 46 fn pam_get_user(pamh: *const PamHandle, user: &*mut c_char, prompt: *const c_char) -> c_int; |
46 item: *const libc::c_void, | |
47 ) -> PamResultCode; | |
48 | |
49 fn pam_get_user( | |
50 pamh: *const PamHandle, | |
51 user: &*mut c_char, | |
52 prompt: *const c_char, | |
53 ) -> PamResultCode; | |
54 | 47 |
55 fn pam_get_authtok( | 48 fn pam_get_authtok( |
56 pamh: *const PamHandle, | 49 pamh: *const PamHandle, |
57 item_type: ItemType, | 50 item_type: c_int, |
58 data: &*mut c_char, | 51 data: &*mut c_char, |
59 prompt: *const c_char, | 52 prompt: *const c_char, |
60 ) -> PamResultCode; | 53 ) -> c_int; |
61 | 54 |
62 } | 55 } |
63 | 56 |
64 pub extern "C" fn cleanup<T>(_: *const PamHandle, c_data: *mut libc::c_void, _: PamResultCode) { | 57 /// Function called at the end of a PAM session that is called to clean up |
58 /// a value previously provided to PAM in a `pam_set_data` call. | |
59 /// | |
60 /// You should never call this yourself. | |
61 extern "C" fn cleanup<T>(_: *const PamHandle, c_data: *mut libc::c_void, _: c_int) { | |
65 unsafe { | 62 unsafe { |
66 let _data: Box<T> = Box::from_raw(c_data.cast::<T>()); | 63 let _data: Box<T> = Box::from_raw(c_data.cast::<T>()); |
67 } | 64 } |
68 } | 65 } |
69 | |
70 pub type PamResult<T> = Result<T, PamResultCode>; | |
71 | 66 |
72 impl PamHandle { | 67 impl PamHandle { |
73 /// Gets some value, identified by `key`, that has been set by the module | 68 /// Gets some value, identified by `key`, that has been set by the module |
74 /// previously. | 69 /// previously. |
75 /// | 70 /// |
85 /// The data stored under the provided key must be of type `T` otherwise the | 80 /// The data stored under the provided key must be of type `T` otherwise the |
86 /// behaviour of this function is undefined. | 81 /// behaviour of this function is undefined. |
87 /// | 82 /// |
88 /// The data, if present, is owned by the current PAM conversation. | 83 /// The data, if present, is owned by the current PAM conversation. |
89 pub unsafe fn get_data<T>(&self, key: &str) -> PamResult<Option<&T>> { | 84 pub unsafe fn get_data<T>(&self, key: &str) -> PamResult<Option<&T>> { |
90 let c_key = CString::new(key).map_err(|_| PamResultCode::PAM_CONV_ERR)?; | 85 let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?; |
91 let mut ptr: *const libc::c_void = std::ptr::null(); | 86 let mut ptr: *const libc::c_void = std::ptr::null(); |
92 to_result(pam_get_data(self, c_key.as_ptr(), &mut ptr))?; | 87 ErrorCode::result_from(pam_get_data(self, c_key.as_ptr(), &mut ptr))?; |
93 match ptr.is_null() { | 88 match ptr.is_null() { |
94 true => Ok(None), | 89 true => Ok(None), |
95 false => { | 90 false => { |
96 let typed_ptr = ptr.cast::<T>(); | 91 let typed_ptr = ptr.cast::<T>(); |
97 Ok(Some(&*typed_ptr)) | 92 Ok(Some(&*typed_ptr)) |
107 /// | 102 /// |
108 /// # Errors | 103 /// # Errors |
109 /// | 104 /// |
110 /// Returns an error if the underlying PAM function call fails. | 105 /// Returns an error if the underlying PAM function call fails. |
111 pub fn set_data<T>(&mut self, key: &str, data: Box<T>) -> PamResult<()> { | 106 pub fn set_data<T>(&mut self, key: &str, data: Box<T>) -> PamResult<()> { |
112 let c_key = CString::new(key).map_err(|_| PamResultCode::PAM_CONV_ERR)?; | 107 let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?; |
113 let res = unsafe { | 108 let ret = unsafe { |
114 pam_set_data( | 109 pam_set_data( |
115 self, | 110 self, |
116 c_key.as_ptr(), | 111 c_key.as_ptr(), |
117 Box::into_raw(data).cast::<libc::c_void>(), | 112 Box::into_raw(data).cast::<libc::c_void>(), |
118 cleanup::<T>, | 113 cleanup::<T>, |
119 ) | 114 ) |
120 }; | 115 }; |
121 to_result(res) | 116 ErrorCode::result_from(ret) |
122 } | 117 } |
123 | 118 |
124 /// Retrieves a value that has been set, possibly by the pam client. | 119 /// Retrieves a value that has been set, possibly by the pam client. |
125 /// This is particularly useful for getting a `PamConv` reference. | 120 /// This is particularly useful for getting a `PamConv` reference. |
126 /// | 121 /// |
134 /// | 129 /// |
135 /// Returns an error if the underlying PAM function call fails. | 130 /// Returns an error if the underlying PAM function call fails. |
136 pub fn get_item<T: crate::items::Item>(&self) -> PamResult<Option<T>> { | 131 pub fn get_item<T: crate::items::Item>(&self) -> PamResult<Option<T>> { |
137 let mut ptr: *const libc::c_void = std::ptr::null(); | 132 let mut ptr: *const libc::c_void = std::ptr::null(); |
138 let out = unsafe { | 133 let out = unsafe { |
139 let r = pam_get_item(self, T::type_id(), &mut ptr); | 134 let ret = pam_get_item(self, T::type_id().into(), &mut ptr); |
140 to_result(r)?; | 135 ErrorCode::result_from(ret)?; |
141 let typed_ptr = ptr.cast::<T::Raw>(); | 136 let typed_ptr = ptr.cast::<T::Raw>(); |
142 match typed_ptr.is_null() { | 137 match typed_ptr.is_null() { |
143 true => None, | 138 true => None, |
144 false => Some(T::from_raw(typed_ptr)), | 139 false => Some(T::from_raw(typed_ptr)), |
145 } | 140 } |
154 /// | 149 /// |
155 /// # Errors | 150 /// # Errors |
156 /// | 151 /// |
157 /// Returns an error if the underlying PAM function call fails. | 152 /// Returns an error if the underlying PAM function call fails. |
158 pub fn set_item<T: Item>(&mut self, item: T) -> PamResult<()> { | 153 pub fn set_item<T: Item>(&mut self, item: T) -> PamResult<()> { |
159 let res = | 154 let ret = |
160 unsafe { pam_set_item(self, T::type_id(), item.into_raw().cast::<libc::c_void>()) }; | 155 unsafe { pam_set_item(self, T::type_id().into(), item.into_raw().cast::<libc::c_void>()) }; |
161 to_result(res) | 156 ErrorCode::result_from(ret) |
162 } | 157 } |
163 | 158 |
164 /// Retrieves the name of the user who is authenticating or logging in. | 159 /// Retrieves the name of the user who is authenticating or logging in. |
165 /// | 160 /// |
166 /// This is really a specialization of `get_item`. | 161 /// This is really a specialization of `get_item`. |
172 /// | 167 /// |
173 /// Returns an error if the underlying PAM function call fails. | 168 /// Returns an error if the underlying PAM function call fails. |
174 pub fn get_user(&self, prompt: Option<&str>) -> PamResult<String> { | 169 pub fn get_user(&self, prompt: Option<&str>) -> PamResult<String> { |
175 let prompt = option_cstr(prompt)?; | 170 let prompt = option_cstr(prompt)?; |
176 let output: *mut c_char = std::ptr::null_mut(); | 171 let output: *mut c_char = std::ptr::null_mut(); |
177 let res = unsafe { pam_get_user(self, &output, prompt_ptr(prompt.as_ref())) }; | 172 let ret = unsafe { pam_get_user(self, &output, prompt_ptr(prompt.as_ref())) }; |
178 match res { | 173 ErrorCode::result_from(ret)?; |
179 PamResultCode::PAM_SUCCESS => copy_pam_string(output), | 174 copy_pam_string(output) |
180 otherwise => Err(otherwise), | |
181 } | |
182 } | 175 } |
183 | 176 |
184 /// Retrieves the authentication token from the user. | 177 /// Retrieves the authentication token from the user. |
185 /// | 178 /// |
186 /// This is really a specialization of `get_item`. | 179 /// This is really a specialization of `get_item`. |
189 /// https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html). | 182 /// https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html). |
190 /// | 183 /// |
191 /// # Errors | 184 /// # Errors |
192 /// | 185 /// |
193 /// Returns an error if the underlying PAM function call fails. | 186 /// Returns an error if the underlying PAM function call fails. |
194 pub fn get_authtok(&self, prompt: Option<&str>) -> PamResult<String> { | 187 pub fn get_authtok(&self, prompt: Option<&str>) -> PamResult<SecureString> { |
195 let prompt = option_cstr(prompt)?; | 188 let prompt = option_cstr(prompt)?; |
196 let output: *mut c_char = std::ptr::null_mut(); | 189 let output: *mut c_char = std::ptr::null_mut(); |
197 let res = unsafe { | 190 let res = unsafe { |
198 pam_get_authtok( | 191 pam_get_authtok( |
199 self, | 192 self, |
200 ItemType::AuthTok, | 193 ItemType::AuthTok.into(), |
201 &output, | 194 &output, |
202 prompt_ptr(prompt.as_ref()), | 195 prompt_ptr(prompt.as_ref()), |
203 ) | 196 ) |
204 }; | 197 }; |
205 to_result(res)?; | 198 ErrorCode::result_from(res)?; |
206 copy_pam_string(output) | 199 copy_pam_string(output).map(SecureString::from) |
207 } | 200 } |
208 } | 201 } |
209 | 202 |
210 /// Safely converts a `&str` option to a `CString` option. | 203 /// Safely converts a `&str` option to a `CString` option. |
211 fn option_cstr(prompt: Option<&str>) -> PamResult<Option<CString>> { | 204 fn option_cstr(prompt: Option<&str>) -> PamResult<Option<CString>> { |
212 prompt | 205 prompt |
213 .map(CString::new) | 206 .map(CString::new) |
214 .transpose() | 207 .transpose() |
215 .map_err(|_| PamResultCode::PAM_CONV_ERR) | 208 .map_err(|_| ErrorCode::ConversationError) |
216 } | 209 } |
217 | 210 |
218 /// The pointer to the prompt CString, or null if absent. | 211 /// The pointer to the prompt CString, or null if absent. |
219 fn prompt_ptr(prompt: Option<&CString>) -> *const c_char { | 212 pub(crate) fn prompt_ptr(prompt: Option<&CString>) -> *const c_char { |
220 match prompt { | 213 match prompt { |
221 Some(c_str) => c_str.as_ptr(), | 214 Some(c_str) => c_str.as_ptr(), |
222 None => std::ptr::null(), | 215 None => std::ptr::null(), |
223 } | 216 } |
224 } | 217 } |
225 | 218 |
226 /// Creates an owned copy of a string that is returned from a | 219 /// Creates an owned copy of a string that is returned from a |
227 /// <code>pam_get_<var>whatever</var></code> function. | 220 /// <code>pam_get_<var>whatever</var></code> function. |
228 fn copy_pam_string(result_ptr: *const c_char) -> PamResult<String> { | 221 pub(crate) fn copy_pam_string(result_ptr: *const c_char) -> PamResult<String> { |
229 // We really shouldn't get a null pointer back here, but if we do, return nothing. | 222 // We really shouldn't get a null pointer back here, but if we do, return nothing. |
230 if result_ptr.is_null() { | 223 if result_ptr.is_null() { |
231 return Ok(String::new()); | 224 return Ok(String::new()); |
232 } | 225 } |
233 let bytes = unsafe { CStr::from_ptr(result_ptr) }; | 226 let bytes = unsafe { CStr::from_ptr(result_ptr) }; |
234 Ok(bytes | 227 bytes |
235 .to_str() | 228 .to_str() |
236 .map_err(|_| PamResultCode::PAM_CONV_ERR)? | 229 .map(String::from) |
237 .into()) | 230 .map_err(|_| ErrorCode::ConversationError) |
238 } | |
239 | |
240 /// Convenience to transform a `PamResultCode` into a unit `PamResult`. | |
241 fn to_result(result: PamResultCode) -> PamResult<()> { | |
242 match result { | |
243 PamResultCode::PAM_SUCCESS => Ok(()), | |
244 otherwise => Err(otherwise), | |
245 } | |
246 } | 231 } |
247 | 232 |
248 /// Provides functions that are invoked by the entrypoints generated by the | 233 /// Provides functions that are invoked by the entrypoints generated by the |
249 /// [`pam_hooks!` macro](../macro.pam_hooks.html). | 234 /// [`pam_hooks!` macro](../macro.pam_hooks.html). |
250 /// | 235 /// |
256 /// This function performs the task of establishing whether the user is permitted to gain access at | 241 /// This function performs the task of establishing whether the user is permitted to gain access at |
257 /// this time. It should be understood that the user has previously been validated by an | 242 /// this time. It should be understood that the user has previously been validated by an |
258 /// authentication module. This function checks for other things. Such things might be: the time of | 243 /// authentication module. This function checks for other things. Such things might be: the time of |
259 /// day or the date, the terminal line, remote hostname, etc. This function may also determine | 244 /// day or the date, the terminal line, remote hostname, etc. This function may also determine |
260 /// things like the expiration on passwords, and respond that the user change it before continuing. | 245 /// things like the expiration on passwords, and respond that the user change it before continuing. |
261 fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { | 246 fn acct_mgmt(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { |
262 PamResultCode::PAM_IGNORE | 247 Err(ErrorCode::Ignore) |
263 } | 248 } |
264 | 249 |
265 /// This function performs the task of authenticating the user. | 250 /// This function performs the task of authenticating the user. |
266 fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { | 251 fn sm_authenticate(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { |
267 PamResultCode::PAM_IGNORE | 252 Err(ErrorCode::Ignore) |
268 } | 253 } |
269 | 254 |
270 /// This function is used to (re-)set the authentication token of the user. | 255 /// This function is used to (re-)set the authentication token of the user. |
271 /// | 256 /// |
272 /// The PAM library calls this function twice in succession. The first time with | 257 /// The PAM library calls this function twice in succession. The first time with |
273 /// `PAM_PRELIM_CHECK` and then, if the module does not return `PAM_TRY_AGAIN`, subsequently with | 258 /// `PAM_PRELIM_CHECK` and then, if the module does not return `PAM_TRY_AGAIN`, subsequently with |
274 /// `PAM_UPDATE_AUTHTOK`. It is only on the second call that the authorization token is | 259 /// `PAM_UPDATE_AUTHTOK`. It is only on the second call that the authorization token is |
275 /// (possibly) changed. | 260 /// (possibly) changed. |
276 fn sm_chauthtok(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { | 261 fn sm_chauthtok(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { |
277 PamResultCode::PAM_IGNORE | 262 Err(ErrorCode::Ignore) |
278 } | 263 } |
279 | 264 |
280 /// This function is called to terminate a session. | 265 /// This function is called to terminate a session. |
281 fn sm_close_session(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { | 266 fn sm_close_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { |
282 PamResultCode::PAM_IGNORE | 267 Err(ErrorCode::Ignore) |
283 } | 268 } |
284 | 269 |
285 /// This function is called to commence a session. | 270 /// This function is called to commence a session. |
286 fn sm_open_session(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { | 271 fn sm_open_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { |
287 PamResultCode::PAM_IGNORE | 272 Err(ErrorCode::Ignore) |
288 } | 273 } |
289 | 274 |
290 /// This function performs the task of altering the credentials of the user with respect to the | 275 /// This function performs the task of altering the credentials of the user with respect to the |
291 /// corresponding authorization scheme. Generally, an authentication module may have access to more | 276 /// corresponding authorization scheme. Generally, an authentication module may have access to more |
292 /// information about a user than their authentication token. This function is used to make such | 277 /// information about a user than their authentication token. This function is used to make such |
293 /// information available to the application. It should only be called after the user has been | 278 /// information available to the application. It should only be called after the user has been |
294 /// authenticated but before a session has been established. | 279 /// authenticated but before a session has been established. |
295 fn sm_setcred(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode { | 280 fn sm_setcred(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { |
296 PamResultCode::PAM_IGNORE | 281 Err(ErrorCode::Ignore) |
297 } | 282 } |
298 } | 283 } |