Mercurial > crates > nonstick
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 } |