Mercurial > crates > nonstick
diff 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 |
line wrap: on
line diff
--- a/src/module.rs Wed Jun 04 03:53:36 2025 -0400 +++ b/src/module.rs Thu Jun 05 03:41:38 2025 -0400 @@ -4,11 +4,7 @@ #![allow(dead_code)] use crate::constants::{ErrorCode, Flags, Result}; -use crate::conv::BinaryData; -use crate::conv::{Conversation, Response}; -use crate::handle::PamModuleHandle; -use crate::pam_ffi::Message; -use secure_string::SecureString; +use crate::handle::PamHandleModule; use std::ffi::CStr; /// A trait for a PAM module to implement. @@ -25,14 +21,14 @@ /// [manpage]: https://www.man7.org/linux/man-pages/man3/pam.3.html /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/Linux-PAM_MWG.html #[allow(unused_variables)] -pub trait PamModule<T: PamModuleHandle> { +pub trait PamModule<T: PamHandleModule> { // Functions for auth modules. /// Authenticate the user. /// /// This is probably the first thing you want to implement. /// In most cases, you will want to get the user and password, - /// using [`PamHandle::get_user`] and [`PamModuleHandle::get_authtok`], + /// using [`PamHandle::get_user`] and [`PamModuleOnly::get_authtok`], /// and verify them against something. /// /// See [the Module Writer's Guide entry for `pam_sm_authenticate`][mwg] @@ -82,7 +78,6 @@ /// See [the Module Writer's Guide entry for `pam_sm_acct_mgmt`][mwg] /// for more information. /// - /// /// # Valid flags /// /// This function may be called with the following flags set: @@ -240,81 +235,6 @@ } } -/// Provides methods to make it easier to send exactly one message. -/// -/// This is primarily used by PAM modules, so that a module that only needs -/// one piece of information at a time doesn't have a ton of boilerplate. -/// You may also find it useful for testing PAM application libraries. -/// -/// ``` -/// # use nonstick::Result; -/// # use nonstick::conv::Conversation; -/// # use nonstick::module::ConversationMux; -/// # fn _do_test(conv: impl Conversation) -> Result<()> { -/// let mut mux = ConversationMux(conv); -/// let token = mux.masked_prompt("enter your one-time token")?; -/// # Ok(()) -/// # } -pub struct ConversationMux<'a, C: Conversation>(pub &'a mut C); - -impl<C: Conversation> Conversation for ConversationMux<'_, C> { - fn send(&mut self, messages: &[Message]) -> Result<Vec<Response>> { - self.0.send(messages) - } -} - -impl<C: Conversation> ConversationMux<'_, C> { - /// Prompts the user for something. - pub fn prompt(&mut self, request: &str) -> Result<String> { - let resp = self.send(&[Message::Prompt(request)])?.pop(); - match resp { - Some(Response::Text(s)) => Ok(s), - _ => Err(ErrorCode::ConversationError), - } - } - - /// Prompts the user for something, but hides what the user types. - pub fn masked_prompt(&mut self, request: &str) -> Result<SecureString> { - let resp = self.send(&[Message::MaskedPrompt(request)])?.pop(); - match resp { - Some(Response::MaskedText(s)) => Ok(s), - _ => Err(ErrorCode::ConversationError), - } - } - - /// Prompts the user for a yes/no/maybe conditional (a Linux-PAM extension). - /// - /// PAM documentation doesn't define the format of the response. - pub fn radio_prompt(&mut self, request: &str) -> Result<String> { - let resp = self.send(&[Message::RadioPrompt(request)])?.pop(); - match resp { - Some(Response::Text(s)) => Ok(s), - _ => Err(ErrorCode::ConversationError), - } - } - - /// Alerts the user to an error. - pub fn error(&mut self, message: &str) { - let _ = self.send(&[Message::Error(message)]); - } - - /// Sends an informational message to the user. - pub fn info(&mut self, message: &str) { - let _ = self.send(&[Message::Info(message)]); - } - - /// Requests binary data from the user (a Linux-PAM extension). - pub fn binary_prompt(&mut self, data: &[u8], data_type: u8) -> Result<BinaryData> { - let resp = self - .send(&[Message::BinaryPrompt { data, data_type }])? - .pop(); - match resp { - Some(Response::Binary(d)) => Ok(d), - _ => Err(ErrorCode::ConversationError), - } - } -} - /// Generates the dynamic library entry points for a [PamModule] implementation. /// /// Calling `pam_hooks!(SomeType)` on a type that implements [PamModule] will @@ -326,25 +246,25 @@ /// Here is full example of a PAM module that would authenticate and authorize everybody: /// /// ```no_run -/// use nonstick::{Flags, LibPamHandle, PamModule, PamModuleHandle, Result as PamResult, pam_hooks}; +/// use nonstick::{Flags, OwnedLibPamHandle, PamModule, PamHandleModule, Result as PamResult, pam_hooks}; /// use std::ffi::CStr; /// # fn main() {} /// /// struct MyPamModule; /// pam_hooks!(MyPamModule); /// -/// impl<T: PamModuleHandle> PamModule<T> for MyPamModule { +/// impl<T: PamHandleModule> PamModule<T> for MyPamModule { /// fn authenticate(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { /// let password = handle.get_authtok(Some("what's your password?"))?; -/// eprintln!("If you say your password is {:?}, who am I to disagree!", password.unsecure()); +/// // You should use a Conversation to communicate with the user +/// // instead of writing to the console, but this is just an example. +/// eprintln!("If you say your password is {password:?}, who am I to disagree?"); /// Ok(()) /// } /// /// fn account_management(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> PamResult<()> { /// let username = handle.get_user(None)?; -/// // You should use a Conversation to communicate with the user -/// // instead of writing to the console, but this is just an example. -/// eprintln!("Hello {username}! I trust you unconditionally!"); +/// eprintln!("Hello {username:?}! I trust you unconditionally!"); /// Ok(()) /// } /// } @@ -462,92 +382,10 @@ #[cfg(test)] mod tests { - use super::{ - Conversation, ConversationMux, ErrorCode, Message, Response, Result, SecureString, - }; - - /// Compile-time test that the `pam_hooks` macro compiles. - mod hooks { - use super::super::{PamModule, PamModuleHandle}; - struct Foo; - impl<T: PamModuleHandle> PamModule<T> for Foo {} - - pam_hooks!(Foo); - } - - #[test] - fn test_mux() { - struct MuxTester; + // Compile-time test that the `pam_hooks` macro compiles. + use super::super::{PamHandleModule, PamModule}; + struct Foo; + impl<T: PamHandleModule> PamModule<T> for Foo {} - impl Conversation for MuxTester { - fn send(&mut self, messages: &[Message]) -> Result<Vec<Response>> { - if let [msg] = messages { - match msg { - Message::Info(info) => { - assert_eq!("let me tell you", *info); - Ok(vec![Response::NoResponse]) - } - Message::Error(error) => { - assert_eq!("oh no", *error); - Ok(vec![Response::NoResponse]) - } - Message::Prompt("should_error") => Err(ErrorCode::BufferError), - Message::Prompt(ask) => { - assert_eq!("question", *ask); - Ok(vec![Response::Text("answer".to_owned())]) - } - Message::MaskedPrompt("return_wrong_type") => { - Ok(vec![Response::NoResponse]) - } - Message::MaskedPrompt(ask) => { - assert_eq!("password!", *ask); - Ok(vec![Response::MaskedText(SecureString::from( - "open sesame", - ))]) - } - Message::BinaryPrompt { data, data_type } => { - assert_eq!(&[1, 2, 3], data); - assert_eq!(69, *data_type); - Ok(vec![Response::Binary(super::BinaryData::new( - vec![3, 2, 1], - 42, - ))]) - } - Message::RadioPrompt(ask) => { - assert_eq!("radio?", *ask); - Ok(vec![Response::Text("yes".to_owned())]) - } - } - } else { - panic!("messages is the wrong size ({len})", len = messages.len()) - } - } - } - - let mut tester = MuxTester; - - let mut mux = ConversationMux(&mut tester); - assert_eq!("answer", mux.prompt("question").unwrap()); - assert_eq!( - SecureString::from("open sesame"), - mux.masked_prompt("password!").unwrap() - ); - mux.error("oh no"); - mux.info("let me tell you"); - { - assert_eq!("yes", mux.radio_prompt("radio?").unwrap()); - assert_eq!( - super::BinaryData::new(vec![3, 2, 1], 42), - mux.binary_prompt(&[1, 2, 3], 69).unwrap(), - ) - } - assert_eq!( - ErrorCode::BufferError, - mux.prompt("should_error").unwrap_err(), - ); - assert_eq!( - ErrorCode::ConversationError, - mux.masked_prompt("return_wrong_type").unwrap_err() - ) - } + pam_hooks!(Foo); }