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
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[..] {