Mercurial > crates > nonstick
diff src/conv.rs @ 87:05291b601f0a
Well and truly separate the Linux extensions.
This separates the Linux extensions on the libpam side,
and disables the two enums on the interface side.
Users can still call the Linux extensions from non-Linux PAM impls,
but they'll get a conversation error back.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Tue, 10 Jun 2025 04:40:01 -0400 |
parents | 2128123b9406 |
children |
line wrap: on
line diff
--- a/src/conv.rs Tue Jun 10 02:43:31 2025 -0400 +++ b/src/conv.rs Tue Jun 10 04:40:01 2025 -0400 @@ -17,10 +17,10 @@ pub enum Message<'a> { Prompt(&'a QAndA<'a>), MaskedPrompt(&'a MaskedQAndA<'a>), + Error(&'a ErrorMsg<'a>), + Info(&'a InfoMsg<'a>), RadioPrompt(&'a RadioQAndA<'a>), BinaryPrompt(&'a BinaryQAndA<'a>), - Error(&'a ErrorMsg<'a>), - Info(&'a InfoMsg<'a>), } impl Message<'_> { @@ -50,24 +50,23 @@ match *self { Message::Prompt(m) => m.set_answer(Err(err)), Message::MaskedPrompt(m) => m.set_answer(Err(err)), + Message::Error(m) => m.set_answer(Err(err)), + Message::Info(m) => m.set_answer(Err(err)), Message::RadioPrompt(m) => m.set_answer(Err(err)), Message::BinaryPrompt(m) => m.set_answer(Err(err)), - Message::Error(m) => m.set_answer(Err(err)), - Message::Info(m) => m.set_answer(Err(err)), } } } macro_rules! q_and_a { - ($name:ident<'a, Q=$qt:ty, A=$at:ty>, $val:path, $($doc:literal)*) => { - $( - #[doc = $doc] - )* + ($(#[$m:meta])* $name:ident<'a, Q=$qt:ty, A=$at:ty>, $val:path) => { + $(#[$m])* pub struct $name<'a> { q: $qt, a: Cell<Result<$at>>, } + $(#[$m])* impl<'a> $name<'a> { #[doc = concat!("Creates a `", stringify!($t), "` to be sent to the user.")] pub fn new(question: $qt) -> Self { @@ -108,6 +107,7 @@ // shout out to stackoverflow user ballpointben for this lazy impl: // https://stackoverflow.com/a/78871280/39808 + $(#[$m])* impl fmt::Debug for $name<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> StdResult<(), fmt::Error> { #[derive(Debug)] @@ -119,44 +119,44 @@ } q_and_a!( + /// A Q&A that asks the user for text and does not show it while typing. + /// + /// In other words, a password entry prompt. MaskedQAndA<'a, Q=&'a str, A=SecureString>, - Message::MaskedPrompt, - "A Q&A that asks the user for text and does not show it while typing." - "" - "In other words, a password entry prompt." + Message::MaskedPrompt ); q_and_a!( + /// A standard Q&A prompt that asks the user for text. + /// + /// This is the normal "ask a person a question" prompt. + /// When the user types, their input will be shown to them. + /// It can be used for things like usernames. QAndA<'a, Q=&'a str, A=String>, - Message::Prompt, - "A standard Q&A prompt that asks the user for text." - "" - "This is the normal \"ask a person a question\" prompt." - "When the user types, their input will be shown to them." - "It can be used for things like usernames." + Message::Prompt ); q_and_a!( + /// A Q&A for "radio button"–style data. (Linux-PAM extension) + /// + /// This message type is theoretically useful for "yes/no/maybe" + /// questions, but nowhere in the documentation is it specified + /// what the format of the answer will be, or how this should be shown. RadioQAndA<'a, Q=&'a str, A=String>, - Message::RadioPrompt, - "A Q&A for \"radio button\"–style data. (Linux-PAM extension)" - "" - "This message type is theoretically useful for \"yes/no/maybe\"" - "questions, but nowhere in the documentation is it specified" - "what the format of the answer will be, or how this should be shown." + Message::RadioPrompt ); q_and_a!( + /// Asks for binary data. (Linux-PAM extension) + /// + /// This sends a binary message to the client application. + /// It can be used to communicate with non-human logins, + /// or to enable things like security keys. + /// + /// The `data_type` tag is a value that is simply passed through + /// to the application. PAM does not define any meaning for it. BinaryQAndA<'a, Q=(&'a [u8], u8), A=BinaryData>, - Message::BinaryPrompt, - "Asks for binary data. (Linux-PAM extension)" - "" - "This sends a binary message to the client application." - "It can be used to communicate with non-human logins," - "or to enable things like security keys." - "" - "The `data_type` tag is a value that is simply passed through" - "to the application. PAM does not define any meaning for it." + Message::BinaryPrompt ); /// Owned binary data. @@ -202,23 +202,23 @@ } q_and_a!( + /// A message containing information to be passed to the user. + /// + /// While this does not have an answer, [`Conversation`] implementations + /// should still call [`set_answer`][`QAndA::set_answer`] to verify that + /// the message has been displayed (or actively discarded). InfoMsg<'a, Q = &'a str, A = ()>, - Message::Info, - "A message containing information to be passed to the user." - "" - "While this does not have an answer, [`Conversation`] implementations" - "should still call [`set_answer`][`QAndA::set_answer`] to verify that" - "the message has been displayed (or actively discarded)." + Message::Info ); q_and_a!( + /// An error message to be passed to the user. + /// + /// While this does not have an answer, [`Conversation`] implementations + /// should still call [`set_answer`][`QAndA::set_answer`] to verify that + /// the message has been displayed (or actively discarded). ErrorMsg<'a, Q = &'a str, A = ()>, - Message::Error, - "An error message to be passed to the user." - "" - "While this does not have an answer, [`Conversation`] implementations" - "should still call [`set_answer`][`QAndA::set_answer`] to verify that" - "the message has been displayed (or actively discarded)." + Message::Error ); /// A channel for PAM modules to request information from the user. @@ -318,10 +318,6 @@ /// # todo!() /// # } /// # -/// # fn radio_prompt(&mut self, request: &str) -> Result<String> { -/// # todo!() -/// # } -/// # /// # fn error_msg(&mut self, message: &str) { /// # todo!() /// # } @@ -330,6 +326,10 @@ /// # todo!() /// # } /// # +/// # fn radio_prompt(&mut self, request: &str) -> Result<String> { +/// # todo!() +/// # } +/// # /// # fn binary_prompt(&mut self, (data, data_type): (&[u8], u8)) -> Result<BinaryData> { /// # todo!() /// # } @@ -356,27 +356,45 @@ fn prompt(&mut self, request: &str) -> Result<String>; /// Prompts the user for something, but hides what the user types. fn masked_prompt(&mut self, request: &str) -> Result<SecureString>; - /// Prompts the user for a yes/no/maybe conditional (a Linux-PAM extension). - /// - /// PAM documentation doesn't define the format of the response. - fn radio_prompt(&mut self, request: &str) -> Result<String>; /// Alerts the user to an error. fn error_msg(&mut self, message: &str); /// Sends an informational message to the user. fn info_msg(&mut self, message: &str); - /// Requests binary data from the user (a Linux-PAM extension). - fn binary_prompt(&mut self, data_and_type: (&[u8], u8)) -> Result<BinaryData>; + /// \[Linux extension] Prompts the user for a yes/no/maybe conditional. + /// + /// PAM documentation doesn't define the format of the response. + /// + /// When called on an implementation that doesn't support radio prompts, + /// this will return [`ErrorCode::ConversationError`]. + /// If implemented on an implementation that doesn't support radio prompts, + /// this will never be called. + fn radio_prompt(&mut self, request: &str) -> Result<String> { + let _ = request; + Err(ErrorCode::ConversationError) + } + /// \[Linux extension] Requests binary data from the user. + /// + /// When called on an implementation that doesn't support radio prompts, + /// this will return [`ErrorCode::ConversationError`]. + /// If implemented on an implementation that doesn't support radio prompts, + /// this will never be called. + fn binary_prompt(&mut self, data_and_type: (&[u8], u8)) -> Result<BinaryData> { + let _ = data_and_type; + Err(ErrorCode::ConversationError) + } } macro_rules! conv_fn { - ($fn_name:ident($($param:tt: $pt:ty),+) -> $resp_type:ty { $msg:ty }) => { + ($(#[$m:meta])* $fn_name:ident($($param:tt: $pt:ty),+) -> $resp_type:ty { $msg:ty }) => { + $(#[$m])* fn $fn_name(&mut self, $($param: $pt),*) -> Result<$resp_type> { let prompt = <$msg>::new($($param),*); self.communicate(&[prompt.message()]); prompt.answer() } }; - ($fn_name:ident($($param:tt: $pt:ty),+) { $msg:ty }) => { + ($(#[$m:meta])*$fn_name:ident($($param:tt: $pt:ty),+) { $msg:ty }) => { + $(#[$m])* fn $fn_name(&mut self, $($param: $pt),*) { self.communicate(&[<$msg>::new($($param),*).message()]); } @@ -386,9 +404,9 @@ impl<C: Conversation> SimpleConversation for C { conv_fn!(prompt(message: &str) -> String { QAndA }); conv_fn!(masked_prompt(message: &str) -> SecureString { MaskedQAndA } ); - conv_fn!(radio_prompt(message: &str) -> String { RadioQAndA }); conv_fn!(error_msg(message: &str) { ErrorMsg }); conv_fn!(info_msg(message: &str) { InfoMsg }); + conv_fn!(radio_prompt(message: &str) -> String { RadioQAndA }); conv_fn!(binary_prompt((data, data_type): (&[u8], u8)) -> BinaryData { BinaryQAndA }); } @@ -427,12 +445,8 @@ #[cfg(test)] mod tests { - use super::{ - BinaryQAndA, Conversation, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA, RadioQAndA, - Result, SecureString, SimpleConversation, - }; + use super::*; use crate::constants::ErrorCode; - use crate::BinaryData; #[test] fn test_demux() { @@ -454,10 +468,6 @@ assert_eq!("reveal", request); Ok(SecureString::from("my secrets")) } - fn radio_prompt(&mut self, request: &str) -> Result<String> { - assert_eq!("channel?", request); - Ok("zero".to_owned()) - } fn error_msg(&mut self, message: &str) { self.error_ran = true; assert_eq!("whoopsie", message); @@ -466,6 +476,10 @@ self.info_ran = true; assert_eq!("did you know", message); } + fn radio_prompt(&mut self, request: &str) -> Result<String> { + assert_eq!("channel?", request); + Ok("zero".to_owned()) + } fn binary_prompt(&mut self, data_and_type: (&[u8], u8)) -> Result<BinaryData> { assert_eq!((&[10, 9, 8][..], 66), data_and_type); Ok(BinaryData::new(vec![5, 5, 5], 5)) @@ -501,15 +515,16 @@ assert!(tester.info_ran); // Test the Linux extensions separately. - - let mut conv = tester.as_conversation(); + { + let mut conv = tester.as_conversation(); - let radio = RadioQAndA::new("channel?"); - let bin = BinaryQAndA::new((&[10, 9, 8], 66)); - conv.communicate(&[radio.message(), bin.message()]); + let radio = RadioQAndA::new("channel?"); + let bin = BinaryQAndA::new((&[10, 9, 8], 66)); + conv.communicate(&[radio.message(), bin.message()]); - assert_eq!("zero", radio.answer().unwrap()); - assert_eq!(BinaryData::from(([5, 5, 5], 5)), bin.answer().unwrap()); + assert_eq!("zero", radio.answer().unwrap()); + assert_eq!(BinaryData::from(([5, 5, 5], 5)), bin.answer().unwrap()); + } } fn test_mux() { @@ -563,6 +578,7 @@ ); tester.error_msg("oh no"); tester.info_msg("let me tell you"); + // Linux-PAM extensions. Always implemented, but separate for clarity. { assert_eq!("yes", tester.radio_prompt("radio?").unwrap()); assert_eq!(