Mercurial > crates > nonstick
comparison src/libpam/answer.rs @ 130:80c07e5ab22f
Transfer over (almost) completely to using libpam-sys.
This reimplements everything in nonstick on top of the new -sys crate.
We don't yet use libpam-sys's helpers for binary message payloads. Soon.
| author | Paul Fisher <paul@pfish.zone> |
|---|---|
| date | Tue, 01 Jul 2025 06:11:43 -0400 |
| parents | e97534be35e3 |
| children | 33b9622ed6d2 |
comparison
equal
deleted
inserted
replaced
| 129:5b2de52dd8b2 | 130:80c07e5ab22f |
|---|---|
| 1 //! Types used to communicate data from the application to the module. | 1 //! Types used to communicate data from the application to the module. |
| 2 | 2 |
| 3 use crate::libpam::conversation::OwnedMessage; | 3 use crate::libpam::conversation::OwnedExchange; |
| 4 use crate::libpam::memory; | 4 use crate::libpam::memory; |
| 5 use crate::libpam::memory::{CBinaryData, CHeapBox, CHeapString}; | 5 use crate::libpam::memory::{CBinaryData, CHeapBox, CHeapString, Immovable}; |
| 6 pub use crate::libpam::pam_ffi::Answer; | |
| 7 use crate::{ErrorCode, Result}; | 6 use crate::{ErrorCode, Result}; |
| 8 use std::ffi::CStr; | 7 use std::ffi::{c_int, c_void, CStr}; |
| 9 use std::mem::ManuallyDrop; | 8 use std::mem::ManuallyDrop; |
| 10 use std::ops::{Deref, DerefMut}; | 9 use std::ops::{Deref, DerefMut}; |
| 11 use std::ptr::NonNull; | 10 use std::ptr::NonNull; |
| 12 use std::{iter, ptr, slice}; | 11 use std::{iter, ptr, slice}; |
| 13 | 12 |
| 20 count: usize, | 19 count: usize, |
| 21 } | 20 } |
| 22 | 21 |
| 23 impl Answers { | 22 impl Answers { |
| 24 /// Builds an Answers out of the given answered Message Q&As. | 23 /// Builds an Answers out of the given answered Message Q&As. |
| 25 pub fn build(value: Vec<OwnedMessage>) -> Result<Self> { | 24 pub fn build(value: Vec<OwnedExchange>) -> Result<Self> { |
| 26 let mut outputs = Self { | 25 let mut outputs = Self { |
| 27 base: memory::calloc(value.len())?, | 26 base: memory::calloc(value.len())?, |
| 28 count: value.len(), | 27 count: value.len(), |
| 29 }; | 28 }; |
| 30 // Even if we fail during this process, we still end up freeing | 29 // Even if we fail during this process, we still end up freeing |
| 31 // all allocated answer memory. | 30 // all allocated answer memory. |
| 32 for (input, output) in iter::zip(value, outputs.iter_mut()) { | 31 for (input, output) in iter::zip(value, outputs.iter_mut()) { |
| 33 match input { | 32 match input { |
| 34 OwnedMessage::MaskedPrompt(p) => TextAnswer::fill(output, p.answer()?.as_ref())?, | 33 OwnedExchange::MaskedPrompt(p) => TextAnswer::fill(output, p.answer()?.as_ref())?, |
| 35 OwnedMessage::Prompt(p) => TextAnswer::fill(output, &(p.answer()?))?, | 34 OwnedExchange::Prompt(p) => TextAnswer::fill(output, &(p.answer()?))?, |
| 36 OwnedMessage::Error(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?, | 35 OwnedExchange::Error(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?, |
| 37 OwnedMessage::Info(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?, | 36 OwnedExchange::Info(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?, |
| 38 // If we're here, that means that we *got* a Linux-PAM | 37 // If we're here, that means that we *got* a Linux-PAM |
| 39 // question from PAM, so we're OK to answer it. | 38 // question from PAM, so we're OK to answer it. |
| 40 OwnedMessage::RadioPrompt(p) => TextAnswer::fill(output, &(p.answer()?))?, | 39 OwnedExchange::RadioPrompt(p) => TextAnswer::fill(output, &(p.answer()?))?, |
| 41 OwnedMessage::BinaryPrompt(p) => BinaryAnswer::fill(output, (&p.answer()?).into())?, | 40 OwnedExchange::BinaryPrompt(p) => { |
| 41 BinaryAnswer::fill(output, (&p.answer()?).into())? | |
| 42 } | |
| 42 } | 43 } |
| 43 } | 44 } |
| 44 Ok(outputs) | 45 Ok(outputs) |
| 45 } | 46 } |
| 46 | 47 |
| 47 /// Converts this into a `*Answer` for passing to PAM. | 48 /// Converts this into a `*Answer` for passing to PAM. |
| 48 /// | 49 /// |
| 49 /// This object is consumed and the `Answer` pointer now owns its data. | 50 /// This object is consumed and the `Answer` pointer now owns its data. |
| 50 /// It can be recreated with [`Self::from_c_heap`]. | 51 /// It can be recreated with [`Self::from_c_heap`]. |
| 51 pub fn into_ptr(self) -> NonNull<Answer> { | 52 pub fn into_ptr(self) -> *mut libpam_sys::pam_response { |
| 52 ManuallyDrop::new(self).base | 53 ManuallyDrop::new(self).base.as_ptr().cast() |
| 53 } | 54 } |
| 54 | 55 |
| 55 /// Takes ownership of a list of answers allocated on the C heap. | 56 /// Takes ownership of a list of answers allocated on the C heap. |
| 56 /// | 57 /// |
| 57 /// # Safety | 58 /// # Safety |
| 58 /// | 59 /// |
| 59 /// It's up to you to make sure you pass a valid pointer, | 60 /// It's up to you to make sure you pass a valid pointer, |
| 60 /// like one that you got from PAM, or maybe [`Self::into_ptr`]. | 61 /// like one that you got from PAM, or maybe [`Self::into_ptr`]. |
| 61 pub unsafe fn from_c_heap(base: NonNull<Answer>, count: usize) -> Self { | 62 pub unsafe fn from_c_heap(base: NonNull<libpam_sys::pam_response>, count: usize) -> Self { |
| 62 Answers { base, count } | 63 Answers { |
| 64 base: NonNull::new_unchecked(base.as_ptr().cast()), | |
| 65 count, | |
| 66 } | |
| 63 } | 67 } |
| 64 } | 68 } |
| 65 | 69 |
| 66 impl Deref for Answers { | 70 impl Deref for Answers { |
| 67 type Target = [Answer]; | 71 type Target = [Answer]; |
| 87 ptr::drop_in_place(answer) | 91 ptr::drop_in_place(answer) |
| 88 } | 92 } |
| 89 memory::free(self.base.as_ptr()) | 93 memory::free(self.base.as_ptr()) |
| 90 } | 94 } |
| 91 } | 95 } |
| 96 } | |
| 97 | |
| 98 /// Generic version of answer data. | |
| 99 /// | |
| 100 /// This has the same structure as [`BinaryAnswer`](crate::libpam::answer::BinaryAnswer) | |
| 101 /// and [`TextAnswer`](crate::libpam::answer::TextAnswer). | |
| 102 #[repr(C)] | |
| 103 #[derive(Debug, Default)] | |
| 104 pub struct Answer { | |
| 105 /// Owned pointer to the data returned in an answer. | |
| 106 /// For most answers, this will be a | |
| 107 /// [`CHeapString`](crate::libpam::memory::CHeapString), | |
| 108 /// but for [`BinaryQAndA`](crate::conv::BinaryQAndA)s | |
| 109 /// (a Linux-PAM extension), this will be a [`CHeapBox`] of | |
| 110 /// [`CBinaryData`](crate::libpam::memory::CBinaryData). | |
| 111 pub data: Option<CHeapBox<c_void>>, | |
| 112 /// Unused. Just here for the padding. | |
| 113 return_code: c_int, | |
| 114 _marker: Immovable, | |
| 92 } | 115 } |
| 93 | 116 |
| 94 #[repr(transparent)] | 117 #[repr(transparent)] |
| 95 #[derive(Debug)] | 118 #[derive(Debug)] |
| 96 pub struct TextAnswer(Answer); | 119 pub struct TextAnswer(Answer); |
| 221 assert_eq!(want, up.contents().unwrap()); | 244 assert_eq!(want, up.contents().unwrap()); |
| 222 up.zero_contents(); | 245 up.zero_contents(); |
| 223 assert_eq!("", up.contents().unwrap()); | 246 assert_eq!("", up.contents().unwrap()); |
| 224 } | 247 } |
| 225 | 248 |
| 226 fn round_trip(msgs: Vec<OwnedMessage>) -> Answers { | 249 fn round_trip(msgs: Vec<OwnedExchange>) -> Answers { |
| 227 let n = msgs.len(); | 250 let n = msgs.len(); |
| 228 let sent = Answers::build(msgs).unwrap(); | 251 let sent = Answers::build(msgs).unwrap(); |
| 229 unsafe { Answers::from_c_heap(sent.into_ptr(), n) } | 252 unsafe { Answers::from_c_heap(NonNull::new_unchecked(sent.into_ptr()), n) } |
| 230 } | 253 } |
| 231 | 254 |
| 232 #[test] | 255 #[test] |
| 233 fn test_round_trip() { | 256 fn test_round_trip() { |
| 234 let mut answers = round_trip(vec![ | 257 let mut answers = round_trip(vec![ |
| 235 answered!(QAndA, OwnedMessage::Prompt, "whats going on".to_owned()), | 258 answered!(QAndA, OwnedExchange::Prompt, "whats going on".to_owned()), |
| 236 answered!(MaskedQAndA, OwnedMessage::MaskedPrompt, "well then".into()), | 259 answered!(MaskedQAndA, OwnedExchange::MaskedPrompt, "well then".into()), |
| 237 answered!(ErrorMsg, OwnedMessage::Error, ()), | 260 answered!(ErrorMsg, OwnedExchange::Error, ()), |
| 238 answered!(InfoMsg, OwnedMessage::Info, ()), | 261 answered!(InfoMsg, OwnedExchange::Info, ()), |
| 239 ]); | 262 ]); |
| 240 | 263 |
| 241 if let [going, well, err, info] = &mut answers[..] { | 264 if let [going, well, err, info] = &mut answers[..] { |
| 242 assert_text_answer("whats going on", going); | 265 assert_text_answer("whats going on", going); |
| 243 assert_text_answer("well then", well); | 266 assert_text_answer("well then", well); |
| 252 fn test_round_trip_linux() { | 275 fn test_round_trip_linux() { |
| 253 use crate::conv::{BinaryData, BinaryQAndA, RadioQAndA}; | 276 use crate::conv::{BinaryData, BinaryQAndA, RadioQAndA}; |
| 254 let binary_msg = { | 277 let binary_msg = { |
| 255 let qa = BinaryQAndA::new((&[][..], 0)); | 278 let qa = BinaryQAndA::new((&[][..], 0)); |
| 256 qa.set_answer(Ok(BinaryData::new(vec![1, 2, 3], 99))); | 279 qa.set_answer(Ok(BinaryData::new(vec![1, 2, 3], 99))); |
| 257 OwnedMessage::BinaryPrompt(qa) | 280 OwnedExchange::BinaryPrompt(qa) |
| 258 }; | 281 }; |
| 259 let mut answers = round_trip(vec![ | 282 let mut answers = round_trip(vec![ |
| 260 binary_msg, | 283 binary_msg, |
| 261 answered!( | 284 answered!( |
| 262 RadioQAndA, | 285 RadioQAndA, |
| 263 OwnedMessage::RadioPrompt, | 286 OwnedExchange::RadioPrompt, |
| 264 "beep boop".to_owned() | 287 "beep boop".to_owned() |
| 265 ), | 288 ), |
| 266 ]); | 289 ]); |
| 267 | 290 |
| 268 if let [bin, radio] = &mut answers[..] { | 291 if let [bin, radio] = &mut answers[..] { |
