comparison src/module.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 c7c596e6388f
comparison
equal deleted inserted replaced
72:47eb242a4f88 73:ac6881304c78
2 2
3 // Temporarily allowed until we get the actual conversation functions hooked up. 3 // Temporarily allowed until we get the actual conversation functions hooked up.
4 #![allow(dead_code)] 4 #![allow(dead_code)]
5 5
6 use crate::constants::{ErrorCode, Flags, Result}; 6 use crate::constants::{ErrorCode, Flags, Result};
7 use crate::conv::BinaryData; 7 use crate::handle::PamHandleModule;
8 use crate::conv::{Conversation, Response};
9 use crate::handle::PamModuleHandle;
10 use crate::pam_ffi::Message;
11 use secure_string::SecureString;
12 use std::ffi::CStr; 8 use std::ffi::CStr;
13 9
14 /// A trait for a PAM module to implement. 10 /// A trait for a PAM module to implement.
15 /// 11 ///
16 /// The default implementations of all these hooks tell PAM to ignore them 12 /// The default implementations of all these hooks tell PAM to ignore them
23 /// and the [PAM Module Writer’s Guide][mwg]. 19 /// and the [PAM Module Writer’s Guide][mwg].
24 /// 20 ///
25 /// [manpage]: https://www.man7.org/linux/man-pages/man3/pam.3.html 21 /// [manpage]: https://www.man7.org/linux/man-pages/man3/pam.3.html
26 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/Linux-PAM_MWG.html 22 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/Linux-PAM_MWG.html
27 #[allow(unused_variables)] 23 #[allow(unused_variables)]
28 pub trait PamModule<T: PamModuleHandle> { 24 pub trait PamModule<T: PamHandleModule> {
29 // Functions for auth modules. 25 // Functions for auth modules.
30 26
31 /// Authenticate the user. 27 /// Authenticate the user.
32 /// 28 ///
33 /// This is probably the first thing you want to implement. 29 /// This is probably the first thing you want to implement.
34 /// In most cases, you will want to get the user and password, 30 /// In most cases, you will want to get the user and password,
35 /// using [`PamHandle::get_user`] and [`PamModuleHandle::get_authtok`], 31 /// using [`PamHandle::get_user`] and [`PamModuleOnly::get_authtok`],
36 /// and verify them against something. 32 /// and verify them against something.
37 /// 33 ///
38 /// See [the Module Writer's Guide entry for `pam_sm_authenticate`][mwg] 34 /// See [the Module Writer's Guide entry for `pam_sm_authenticate`][mwg]
39 /// for more information. 35 /// for more information.
40 /// 36 ///
80 /// or really do whatever you want. 76 /// or really do whatever you want.
81 /// 77 ///
82 /// See [the Module Writer's Guide entry for `pam_sm_acct_mgmt`][mwg] 78 /// See [the Module Writer's Guide entry for `pam_sm_acct_mgmt`][mwg]
83 /// for more information. 79 /// for more information.
84 /// 80 ///
85 ///
86 /// # Valid flags 81 /// # Valid flags
87 /// 82 ///
88 /// This function may be called with the following flags set: 83 /// This function may be called with the following flags set:
89 /// 84 ///
90 /// - [`Flags::SILENT`] 85 /// - [`Flags::SILENT`]
238 fn close_session(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> Result<()> { 233 fn close_session(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> Result<()> {
239 Err(ErrorCode::Ignore) 234 Err(ErrorCode::Ignore)
240 } 235 }
241 } 236 }
242 237
243 /// Provides methods to make it easier to send exactly one message.
244 ///
245 /// This is primarily used by PAM modules, so that a module that only needs
246 /// one piece of information at a time doesn't have a ton of boilerplate.
247 /// You may also find it useful for testing PAM application libraries.
248 ///
249 /// ```
250 /// # use nonstick::Result;
251 /// # use nonstick::conv::Conversation;
252 /// # use nonstick::module::ConversationMux;
253 /// # fn _do_test(conv: impl Conversation) -> Result<()> {
254 /// let mut mux = ConversationMux(conv);
255 /// let token = mux.masked_prompt("enter your one-time token")?;
256 /// # Ok(())
257 /// # }
258 pub struct ConversationMux<'a, C: Conversation>(pub &'a mut C);
259
260 impl<C: Conversation> Conversation for ConversationMux<'_, C> {
261 fn send(&mut self, messages: &[Message]) -> Result<Vec<Response>> {
262 self.0.send(messages)
263 }
264 }
265
266 impl<C: Conversation> ConversationMux<'_, C> {
267 /// Prompts the user for something.
268 pub fn prompt(&mut self, request: &str) -> Result<String> {
269 let resp = self.send(&[Message::Prompt(request)])?.pop();
270 match resp {
271 Some(Response::Text(s)) => Ok(s),
272 _ => Err(ErrorCode::ConversationError),
273 }
274 }
275
276 /// Prompts the user for something, but hides what the user types.
277 pub fn masked_prompt(&mut self, request: &str) -> Result<SecureString> {
278 let resp = self.send(&[Message::MaskedPrompt(request)])?.pop();
279 match resp {
280 Some(Response::MaskedText(s)) => Ok(s),
281 _ => Err(ErrorCode::ConversationError),
282 }
283 }
284
285 /// Prompts the user for a yes/no/maybe conditional (a Linux-PAM extension).
286 ///
287 /// PAM documentation doesn't define the format of the response.
288 pub fn radio_prompt(&mut self, request: &str) -> Result<String> {
289 let resp = self.send(&[Message::RadioPrompt(request)])?.pop();
290 match resp {
291 Some(Response::Text(s)) => Ok(s),
292 _ => Err(ErrorCode::ConversationError),
293 }
294 }
295
296 /// Alerts the user to an error.
297 pub fn error(&mut self, message: &str) {
298 let _ = self.send(&[Message::Error(message)]);
299 }
300
301 /// Sends an informational message to the user.
302 pub fn info(&mut self, message: &str) {
303 let _ = self.send(&[Message::Info(message)]);
304 }
305
306 /// Requests binary data from the user (a Linux-PAM extension).
307 pub fn binary_prompt(&mut self, data: &[u8], data_type: u8) -> Result<BinaryData> {
308 let resp = self
309 .send(&[Message::BinaryPrompt { data, data_type }])?
310 .pop();
311 match resp {
312 Some(Response::Binary(d)) => Ok(d),
313 _ => Err(ErrorCode::ConversationError),
314 }
315 }
316 }
317
318 /// Generates the dynamic library entry points for a [PamModule] implementation. 238 /// Generates the dynamic library entry points for a [PamModule] implementation.
319 /// 239 ///
320 /// Calling `pam_hooks!(SomeType)` on a type that implements [PamModule] will 240 /// Calling `pam_hooks!(SomeType)` on a type that implements [PamModule] will
321 /// generate the exported `extern "C"` functions that PAM uses to call into 241 /// generate the exported `extern "C"` functions that PAM uses to call into
322 /// your module. 242 /// your module.
324 /// ## Examples: 244 /// ## Examples:
325 /// 245 ///
326 /// Here is full example of a PAM module that would authenticate and authorize everybody: 246 /// Here is full example of a PAM module that would authenticate and authorize everybody:
327 /// 247 ///
328 /// ```no_run 248 /// ```no_run
329 /// use nonstick::{Flags, LibPamHandle, PamModule, PamModuleHandle, Result as PamResult, pam_hooks}; 249 /// use nonstick::{Flags, OwnedLibPamHandle, PamModule, PamHandleModule, Result as PamResult, pam_hooks};
330 /// use std::ffi::CStr; 250 /// use std::ffi::CStr;
331 /// # fn main() {} 251 /// # fn main() {}
332 /// 252 ///
333 /// struct MyPamModule; 253 /// struct MyPamModule;
334 /// pam_hooks!(MyPamModule); 254 /// pam_hooks!(MyPamModule);
335 /// 255 ///
336 /// impl<T: PamModuleHandle> PamModule<T> for MyPamModule { 256 /// impl<T: PamHandleModule> PamModule<T> for MyPamModule {
337 /// fn authenticate(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { 257 /// fn authenticate(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
338 /// let password = handle.get_authtok(Some("what's your password?"))?; 258 /// let password = handle.get_authtok(Some("what's your password?"))?;
339 /// eprintln!("If you say your password is {:?}, who am I to disagree!", password.unsecure()); 259 /// // You should use a Conversation to communicate with the user
260 /// // instead of writing to the console, but this is just an example.
261 /// eprintln!("If you say your password is {password:?}, who am I to disagree?");
340 /// Ok(()) 262 /// Ok(())
341 /// } 263 /// }
342 /// 264 ///
343 /// fn account_management(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { 265 /// fn account_management(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
344 /// let username = handle.get_user(None)?; 266 /// let username = handle.get_user(None)?;
345 /// // You should use a Conversation to communicate with the user 267 /// eprintln!("Hello {username:?}! I trust you unconditionally!");
346 /// // instead of writing to the console, but this is just an example.
347 /// eprintln!("Hello {username}! I trust you unconditionally!");
348 /// Ok(()) 268 /// Ok(())
349 /// } 269 /// }
350 /// } 270 /// }
351 /// ``` 271 /// ```
352 #[macro_export] 272 #[macro_export]
460 }; 380 };
461 } 381 }
462 382
463 #[cfg(test)] 383 #[cfg(test)]
464 mod tests { 384 mod tests {
465 use super::{ 385 // Compile-time test that the `pam_hooks` macro compiles.
466 Conversation, ConversationMux, ErrorCode, Message, Response, Result, SecureString, 386 use super::super::{PamHandleModule, PamModule};
467 }; 387 struct Foo;
468 388 impl<T: PamHandleModule> PamModule<T> for Foo {}
469 /// Compile-time test that the `pam_hooks` macro compiles. 389
470 mod hooks { 390 pam_hooks!(Foo);
471 use super::super::{PamModule, PamModuleHandle};
472 struct Foo;
473 impl<T: PamModuleHandle> PamModule<T> for Foo {}
474
475 pam_hooks!(Foo);
476 }
477
478 #[test]
479 fn test_mux() {
480 struct MuxTester;
481
482 impl Conversation for MuxTester {
483 fn send(&mut self, messages: &[Message]) -> Result<Vec<Response>> {
484 if let [msg] = messages {
485 match msg {
486 Message::Info(info) => {
487 assert_eq!("let me tell you", *info);
488 Ok(vec![Response::NoResponse])
489 }
490 Message::Error(error) => {
491 assert_eq!("oh no", *error);
492 Ok(vec![Response::NoResponse])
493 }
494 Message::Prompt("should_error") => Err(ErrorCode::BufferError),
495 Message::Prompt(ask) => {
496 assert_eq!("question", *ask);
497 Ok(vec![Response::Text("answer".to_owned())])
498 }
499 Message::MaskedPrompt("return_wrong_type") => {
500 Ok(vec![Response::NoResponse])
501 }
502 Message::MaskedPrompt(ask) => {
503 assert_eq!("password!", *ask);
504 Ok(vec![Response::MaskedText(SecureString::from(
505 "open sesame",
506 ))])
507 }
508 Message::BinaryPrompt { data, data_type } => {
509 assert_eq!(&[1, 2, 3], data);
510 assert_eq!(69, *data_type);
511 Ok(vec![Response::Binary(super::BinaryData::new(
512 vec![3, 2, 1],
513 42,
514 ))])
515 }
516 Message::RadioPrompt(ask) => {
517 assert_eq!("radio?", *ask);
518 Ok(vec![Response::Text("yes".to_owned())])
519 }
520 }
521 } else {
522 panic!("messages is the wrong size ({len})", len = messages.len())
523 }
524 }
525 }
526
527 let mut tester = MuxTester;
528
529 let mut mux = ConversationMux(&mut tester);
530 assert_eq!("answer", mux.prompt("question").unwrap());
531 assert_eq!(
532 SecureString::from("open sesame"),
533 mux.masked_prompt("password!").unwrap()
534 );
535 mux.error("oh no");
536 mux.info("let me tell you");
537 {
538 assert_eq!("yes", mux.radio_prompt("radio?").unwrap());
539 assert_eq!(
540 super::BinaryData::new(vec![3, 2, 1], 42),
541 mux.binary_prompt(&[1, 2, 3], 69).unwrap(),
542 )
543 }
544 assert_eq!(
545 ErrorCode::BufferError,
546 mux.prompt("should_error").unwrap_err(),
547 );
548 assert_eq!(
549 ErrorCode::ConversationError,
550 mux.masked_prompt("return_wrong_type").unwrap_err()
551 )
552 }
553 } 391 }