Mercurial > crates > nonstick
comparison src/handle.rs @ 73:ac6881304c78
Do conversations, along with way too much stuff.
This implements conversations, along with all the memory management
brouhaha that goes along with it. The conversation now lives directly
on the handle rather than being a thing you have to get from it
and then call manually. It Turns Out this makes things a lot easier!
I guess we reorganized things again. For the last time. For real.
I promise.
This all passes ASAN, so it seems Pretty Good!
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Thu, 05 Jun 2025 03:41:38 -0400 |
parents | 47eb242a4f88 |
children | c30811b4afae |
comparison
equal
deleted
inserted
replaced
72:47eb242a4f88 | 73:ac6881304c78 |
---|---|
1 //! The wrapper types and traits for handles into the PAM library. | 1 //! The wrapper types and traits for handles into the PAM library. |
2 use crate::constants::{ErrorCode, Result}; | 2 use crate::constants::Result; |
3 use crate::conv::Conversation; | 3 use crate::conv::Conversation; |
4 use crate::items::ItemType; | |
5 use crate::module::ConversationMux; | |
6 use crate::pam_ffi; | |
7 use crate::pam_ffi::{memory, LibPamConversation, LibPamHandle}; | |
8 use std::ffi::{c_char, c_int}; | |
9 use std::{mem, ptr}; | |
10 | 4 |
11 macro_rules! trait_item { | 5 macro_rules! trait_item { |
12 (get = $getter:ident, item = $item:literal $(, see = $see:path)? $(, $($doc:literal)*)?) => { | 6 (get = $getter:ident, item = $item:literal $(, see = $see:path)? $(, $($doc:literal)*)?) => { |
13 $( | 7 $( |
14 $(#[doc = $doc])* | 8 $(#[doc = $doc])* |
55 #[doc = "[mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_item"] | 49 #[doc = "[mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_item"] |
56 fn $setter(&mut self, value: Option<&str>) -> Result<()>; | 50 fn $setter(&mut self, value: Option<&str>) -> Result<()>; |
57 }; | 51 }; |
58 } | 52 } |
59 | 53 |
60 /// Features of a PAM handle that are available to applications and modules. | 54 /// All-in-one trait for what you should expect from PAM as an application. |
61 /// | 55 pub trait PamHandleApplication: PamApplicationOnly + PamShared {} |
62 /// You probably want [`LibPamHandle`]. This trait is intended to allow creating | 56 impl<T> PamHandleApplication for T where T: PamApplicationOnly + PamShared {} |
63 /// mock PAM handle types used for testing PAM modules and applications. | 57 |
64 pub trait PamHandle { | 58 /// All-in-one trait for what you should expect from PAM as a module. |
65 type Conv: Conversation; | 59 pub trait PamHandleModule: PamModuleOnly + PamShared {} |
60 impl<T> PamHandleModule for T where T: PamModuleOnly + PamShared {} | |
61 | |
62 /// Functionality for both PAM applications and PAM modules. | |
63 /// | |
64 /// This base trait includes features of a PAM handle that are available | |
65 /// to both applications and modules. | |
66 /// | |
67 /// You probably want [`LibPamHandle`](crate::pam_ffi::OwnedLibPamHandle). | |
68 /// This trait is intended to allow creating mock PAM handle types | |
69 /// to test PAM modules and applications. | |
70 pub trait PamShared { | |
66 /// Retrieves the name of the user who is authenticating or logging in. | 71 /// Retrieves the name of the user who is authenticating or logging in. |
67 /// | 72 /// |
68 /// If the username has previously been obtained, this uses that username; | 73 /// If the username has previously been obtained, this uses that username; |
69 /// otherwise it prompts the user with the first of these that is present: | 74 /// otherwise it prompts the user with the first of these that is present: |
70 /// | 75 /// |
71 /// 1. The prompt string passed to this function. | 76 /// 1. The prompt string passed to this function. |
72 /// 2. The string returned by `get_user_prompt_item`. | 77 /// 2. The string returned by `get_user_prompt_item`. |
73 /// 3. The default prompt, `login: ` | 78 /// 3. The default prompt, `login: `. |
74 /// | 79 /// |
75 /// See the [`pam_get_user` manual page][man] | 80 /// See the [`pam_get_user` manual page][man] |
76 /// or [`pam_get_user` in the Module Writer's Guide][mwg]. | 81 /// or [`pam_get_user` in the Module Writer's Guide][mwg]. |
77 /// | 82 /// |
78 /// # Example | 83 /// # Example |
79 /// | 84 /// |
80 /// ```no_run | 85 /// ```no_run |
81 /// # use nonstick::PamModuleHandle; | 86 /// # use nonstick::PamShared; |
82 /// # fn _doc(handle: &mut impl PamModuleHandle) -> Result<(), Box<dyn std::error::Error>> { | 87 /// # fn _doc(handle: &mut impl PamShared) -> Result<(), Box<dyn std::error::Error>> { |
83 /// // Get the username using the default prompt. | 88 /// // Get the username using the default prompt. |
84 /// let user = handle.get_user(None)?; | 89 /// let user = handle.get_user(None)?; |
85 /// // Get the username using a custom prompt. | 90 /// // Get the username using a custom prompt. |
86 /// // If this were actually called right after the above, | 91 /// // If this were actually called right after the above, |
87 /// // both user and user_2 would have the same value. | 92 /// // both user and user_2 would have the same value. |
90 /// # } | 95 /// # } |
91 /// ``` | 96 /// ``` |
92 /// | 97 /// |
93 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html | 98 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html |
94 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_user | 99 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_user |
95 fn get_user(&mut self, prompt: Option<&str>) -> Result<Option<&str>>; | 100 fn get_user(&mut self, prompt: Option<&str>) -> Result<&str>; |
96 | 101 |
97 trait_item!( | 102 trait_item!( |
98 get = user_item, | 103 get = user_item, |
99 item = "PAM_USER", | 104 item = "PAM_USER", |
105 see = Self::get_user, | |
100 "The identity of the user for whom service is being requested." | 106 "The identity of the user for whom service is being requested." |
101 "" | 107 "" |
102 "While PAM usually sets this automatically during the course of " | 108 "Unlike [`get_user`](Self::get_user), this will simply get" |
103 "a [`get_user`](Self::get_user) call, it may be changed by a module " | 109 "the current state of the user item, and not request the username. " |
104 "over the course of the PAM transaction." | 110 "While PAM usually sets this automatically in the `get_user` call, " |
111 "it may be changed by a module during the PAM transaction. " | |
105 "Applications should check it after each step of the PAM process." | 112 "Applications should check it after each step of the PAM process." |
106 ); | 113 ); |
107 trait_item!( | 114 trait_item!( |
108 set = set_user_item, | 115 set = set_user_item, |
109 item = "PAM_USER", | 116 item = "PAM_USER", |
223 /// Functionality of a PAM handle that can be expected by a PAM application. | 230 /// Functionality of a PAM handle that can be expected by a PAM application. |
224 /// | 231 /// |
225 /// If you are not writing a PAM client application (e.g., you are writing | 232 /// If you are not writing a PAM client application (e.g., you are writing |
226 /// a module), you should not use the functionality exposed by this trait. | 233 /// a module), you should not use the functionality exposed by this trait. |
227 /// | 234 /// |
228 /// Like [`PamHandle`], this is intended to allow creating mock implementations | 235 /// Like [`PamShared`], this is intended to allow creating mock implementations |
229 /// of PAM for testing PAM applications. | 236 /// of PAM for testing PAM applications. |
230 pub trait PamApplicationHandle: PamHandle { | 237 pub trait PamApplicationOnly { |
231 /// Closes the PAM session on an owned PAM handle. | 238 /// Closes the PAM session on an owned PAM handle. |
232 /// | 239 /// |
233 /// This should be called with the result of the application's last call | 240 /// This should be called with the result of the application's last call |
234 /// into PAM services. Since this is only applicable to *owned* PAM handles, | 241 /// into PAM services. Since this is only applicable to *owned* PAM handles, |
235 /// a PAM module should never call this (and it will never be handed | 242 /// a PAM module should never call this (and it will never be handed |
236 /// an owned `PamHandle` that it can `close`). | 243 /// an owned `PamHandle` that it can `close`). |
237 /// | 244 /// |
238 /// See the [`pam_end` manual page][man] for more information. | 245 /// See the [`pam_end` manual page][man] for more information. |
239 /// | 246 /// |
240 /// ```no_run | 247 /// ```no_run |
241 /// # use nonstick::PamApplicationHandle; | 248 /// # use nonstick::handle::PamApplicationOnly; |
242 /// # use std::error::Error; | 249 /// # use std::error::Error; |
243 /// # fn _doc(handle: impl PamApplicationHandle, auth_result: nonstick::Result<()>) -> Result<(), Box<dyn Error>> { | 250 /// # fn _doc(handle: impl PamApplicationOnly, auth_result: nonstick::Result<()>) -> Result<(), Box<dyn Error>> { |
244 /// // Earlier: authentication was performed and the result was stored | 251 /// // Earlier: authentication was performed and the result was stored |
245 /// // into auth_result. | 252 /// // into auth_result. |
246 /// handle.close(auth_result)?; | 253 /// handle.close(auth_result)?; |
247 /// # Ok(()) | 254 /// # Ok(()) |
248 /// # } | 255 /// # } |
249 /// ``` | 256 /// ``` |
250 /// | 257 /// |
251 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_end.3.html | 258 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_end.3.html |
252 fn close(self, status: Result<()>) -> Result<()>; | 259 fn close(self, status: Result<()>) -> Result<()>; |
253 | |
254 /// Uses a new PAM conversation. | |
255 fn set_conversation(&mut self, conversation: Self::Conv) -> Result<()>; | |
256 } | 260 } |
257 | 261 |
258 /// Functionality of a PAM handle that can be expected by a PAM module. | 262 /// Functionality of a PAM handle that can be expected by a PAM module. |
259 /// | 263 /// |
260 /// If you are not writing a PAM module (e.g., you are writing an application), | 264 /// If you are not writing a PAM module (e.g., you are writing an application), |
261 /// you should not use any of the functionality exposed by this trait. | 265 /// you should not use any of the functionality exposed by this trait. |
262 /// | 266 /// |
263 /// Like [`PamHandle`], this is intended to allow creating mock implementations | 267 /// Like [`PamShared`], this is intended to allow creating mock implementations |
264 /// of PAM for testing PAM modules. | 268 /// of PAM for testing PAM modules. |
265 pub trait PamModuleHandle: PamHandle { | 269 pub trait PamModuleOnly: Conversation { |
266 /// Gets a channel for communication with the user. | |
267 /// | |
268 /// The Conversation is the conduit which you use for all communication | |
269 /// with the user. | |
270 fn conversation(&mut self) -> Result<ConversationMux<'_, Self::Conv>>; | |
271 | |
272 /// Retrieves the authentication token from the user. | 270 /// Retrieves the authentication token from the user. |
273 /// | 271 /// |
274 /// This should only be used by *authentication* and *password-change* | 272 /// This should only be used by *authentication* and *password-change* |
275 /// PAM modules. | 273 /// PAM modules. |
276 /// | 274 /// |
278 /// or [`pam_get_item` in the Module Writer's Guide][mwg]. | 276 /// or [`pam_get_item` in the Module Writer's Guide][mwg]. |
279 /// | 277 /// |
280 /// # Example | 278 /// # Example |
281 /// | 279 /// |
282 /// ```no_run | 280 /// ```no_run |
283 /// # use nonstick::PamModuleHandle; | 281 /// # use nonstick::handle::PamModuleOnly; |
284 /// # fn _doc(handle: &mut impl PamModuleHandle) -> Result<(), Box<dyn std::error::Error>> { | 282 /// # fn _doc(handle: &mut impl PamModuleOnly) -> Result<(), Box<dyn std::error::Error>> { |
285 /// // Get the user's password using the default prompt. | 283 /// // Get the user's password using the default prompt. |
286 /// let pass = handle.get_authtok(None)?; | 284 /// let pass = handle.get_authtok(None)?; |
287 /// // Get the user's password using a custom prompt. | 285 /// // Get the user's password using a custom prompt. |
288 /// let pass = handle.get_authtok(Some("Reveal your secrets!"))?; | 286 /// let pass = handle.get_authtok(Some("Reveal your secrets!"))?; |
289 /// Ok(()) | 287 /// Ok(()) |
290 /// # } | 288 /// # } |
291 /// ``` | 289 /// ``` |
292 /// | 290 /// |
293 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html | 291 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html |
294 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item | 292 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item |
295 fn get_authtok(&mut self, prompt: Option<&str>) -> Result<Option<&str>>; | 293 fn get_authtok(&mut self, prompt: Option<&str>) -> Result<&str>; |
296 | 294 |
297 trait_item!( | 295 trait_item!( |
298 get = authtok_item, | 296 get = authtok_item, |
299 item = "PAM_AUTHTOK", | 297 item = "PAM_AUTHTOK", |
300 see = Self::get_authtok, | 298 see = Self::get_authtok, |
349 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html | 347 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html |
350 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_data | 348 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_data |
351 fn set_data<T>(&mut self, key: &str, data: Box<T>) -> Result<()>; | 349 fn set_data<T>(&mut self, key: &str, data: Box<T>) -> Result<()>; |
352 */ | 350 */ |
353 } | 351 } |
354 | |
355 | |
356 impl LibPamHandle { | |
357 /// Gets a C string item. | |
358 /// | |
359 /// # Safety | |
360 /// | |
361 /// You better be requesting an item which is a C string. | |
362 unsafe fn get_cstr_item(&mut self, item_type: ItemType) -> Result<Option<&str>> { | |
363 let mut output = ptr::null(); | |
364 let ret = unsafe { pam_ffi::pam_get_item(self, item_type as c_int, &mut output) }; | |
365 ErrorCode::result_from(ret)?; | |
366 memory::wrap_string(output.cast()) | |
367 } | |
368 | |
369 /// Sets a C string item. | |
370 /// | |
371 /// # Safety | |
372 /// | |
373 /// You better be setting an item which is a C string. | |
374 unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&str>) -> Result<()> { | |
375 let data_str = memory::option_cstr(data)?; | |
376 let ret = unsafe { | |
377 pam_ffi::pam_set_item( | |
378 self, | |
379 item_type as c_int, | |
380 memory::prompt_ptr(data_str.as_ref()).cast(), | |
381 ) | |
382 }; | |
383 ErrorCode::result_from(ret) | |
384 } | |
385 } | |
386 | |
387 impl Drop for LibPamHandle { | |
388 /// Ends the PAM session with a zero error code. | |
389 /// You probably want to call [`close`](Self::close) instead of | |
390 /// letting this drop by itself. | |
391 fn drop(&mut self) { | |
392 unsafe { | |
393 pam_ffi::pam_end(self, 0); | |
394 } | |
395 } | |
396 } | |
397 | |
398 macro_rules! cstr_item { | |
399 (get = $getter:ident, item = $item_type:path) => { | |
400 fn $getter(&mut self) -> Result<Option<&str>> { | |
401 unsafe { self.get_cstr_item($item_type) } | |
402 } | |
403 }; | |
404 (set = $setter:ident, item = $item_type:path) => { | |
405 fn $setter(&mut self, value: Option<&str>) -> Result<()> { | |
406 unsafe { self.set_cstr_item($item_type, value) } | |
407 } | |
408 }; | |
409 } | |
410 | |
411 impl PamHandle for LibPamHandle { | |
412 type Conv = LibPamConversation; | |
413 fn get_user(&mut self, prompt: Option<&str>) -> Result<Option<&str>> { | |
414 let prompt = memory::option_cstr(prompt)?; | |
415 let mut output: *const c_char = ptr::null(); | |
416 let ret = unsafe { | |
417 pam_ffi::pam_get_user(self, &mut output, memory::prompt_ptr(prompt.as_ref())) | |
418 }; | |
419 ErrorCode::result_from(ret)?; | |
420 unsafe { memory::wrap_string(output) } | |
421 } | |
422 | |
423 cstr_item!(get = user_item, item = ItemType::User); | |
424 cstr_item!(set = set_user_item, item = ItemType::User); | |
425 cstr_item!(get = service, item = ItemType::Service); | |
426 cstr_item!(set = set_service, item = ItemType::Service); | |
427 cstr_item!(get = user_prompt, item = ItemType::UserPrompt); | |
428 cstr_item!(set = set_user_prompt, item = ItemType::UserPrompt); | |
429 cstr_item!(get = tty_name, item = ItemType::Tty); | |
430 cstr_item!(set = set_tty_name, item = ItemType::Tty); | |
431 cstr_item!(get = remote_user, item = ItemType::RemoteUser); | |
432 cstr_item!(set = set_remote_user, item = ItemType::RemoteUser); | |
433 cstr_item!(get = remote_host, item = ItemType::RemoteHost); | |
434 cstr_item!(set = set_remote_host, item = ItemType::RemoteHost); | |
435 cstr_item!(set = set_authtok_item, item = ItemType::AuthTok); | |
436 cstr_item!(set = set_old_authtok_item, item = ItemType::OldAuthTok); | |
437 } | |
438 | |
439 impl PamApplicationHandle for LibPamHandle { | |
440 fn close(mut self, status: Result<()>) -> Result<()> { | |
441 let result = unsafe { pam_ffi::pam_end(&mut self, ErrorCode::result_to_c(status)) }; | |
442 // Since we've already `pam_end`ed this session, we don't want it to be | |
443 // double-freed on drop. | |
444 mem::forget(self); | |
445 ErrorCode::result_from(result) | |
446 } | |
447 | |
448 fn set_conversation(&mut self, conversation: Self::Conv) -> Result<()> { | |
449 todo!() | |
450 } | |
451 } | |
452 | |
453 impl PamModuleHandle for LibPamHandle { | |
454 fn conversation(&mut self) -> Result<ConversationMux<'_, Self::Conv>> { | |
455 todo!() | |
456 } | |
457 | |
458 fn get_authtok(&mut self, prompt: Option<&str>) -> Result<Option<&str>> { | |
459 let prompt = memory::option_cstr(prompt)?; | |
460 let mut output: *const c_char = ptr::null_mut(); | |
461 let res = unsafe { | |
462 pam_ffi::pam_get_authtok( | |
463 self, | |
464 ItemType::AuthTok.into(), | |
465 &mut output, | |
466 memory::prompt_ptr(prompt.as_ref()), | |
467 ) | |
468 }; | |
469 ErrorCode::result_from(res)?; | |
470 unsafe { memory::wrap_string(output) } | |
471 } | |
472 | |
473 cstr_item!(get = authtok_item, item = ItemType::AuthTok); | |
474 cstr_item!(get = old_authtok_item, item = ItemType::OldAuthTok); | |
475 } | |
476 | |
477 /// Function called at the end of a PAM session that is called to clean up | |
478 /// a value previously provided to PAM in a `pam_set_data` call. | |
479 /// | |
480 /// You should never call this yourself. | |
481 extern "C" fn set_data_cleanup<T>(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) { | |
482 unsafe { | |
483 let _data: Box<T> = Box::from_raw(c_data.cast()); | |
484 } | |
485 } |