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 }