Mercurial > crates > nonstick
comparison src/libpam/answer.rs @ 78:002adfb98c5c
Rename files, reorder structs, remove annoying BorrowedBinaryData type.
This is basically a cleanup change. Also it adds tests.
- Renames the files with Questions and Answers to question and answer.
- Reorders the structs in those files to put the important ones first.
- Removes the BorrowedBinaryData type. It was a bad idea all along.
Instead, we just use (&[u8], u8).
- Adds some tests because I just can't help myself.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sun, 08 Jun 2025 03:48:40 -0400 |
parents | src/libpam/response.rs@351bdc13005e |
children | 2128123b9406 |
comparison
equal
deleted
inserted
replaced
77:351bdc13005e | 78:002adfb98c5c |
---|---|
1 //! Types used to communicate data from the application to the module. | |
2 | |
3 use crate::libpam::conversation::OwnedMessage; | |
4 use crate::libpam::memory; | |
5 use crate::libpam::memory::{CBinaryData, Immovable}; | |
6 use crate::{ErrorCode, Result}; | |
7 use std::ffi::{c_int, c_void, CStr}; | |
8 use std::ops::{Deref, DerefMut}; | |
9 use std::{iter, mem, ptr, slice}; | |
10 | |
11 /// The corridor via which the answer to Messages navigate through PAM. | |
12 #[derive(Debug)] | |
13 pub struct Answers { | |
14 base: *mut Answer, | |
15 count: usize, | |
16 } | |
17 | |
18 impl Answers { | |
19 /// Builds an Answers out of the given answered Message Q&As. | |
20 pub fn build(value: Vec<OwnedMessage>) -> Result<Self> { | |
21 let mut outputs = Self { | |
22 base: memory::calloc(value.len()), | |
23 count: value.len(), | |
24 }; | |
25 // Even if we fail during this process, we still end up freeing | |
26 // all allocated answer memory. | |
27 for (input, output) in iter::zip(value, outputs.iter_mut()) { | |
28 match input { | |
29 OwnedMessage::MaskedPrompt(p) => TextAnswer::fill(output, p.answer()?.unsecure())?, | |
30 OwnedMessage::Prompt(p) => TextAnswer::fill(output, &(p.answer()?))?, | |
31 OwnedMessage::BinaryPrompt(p) => BinaryAnswer::fill(output, (&p.answer()?).into())?, | |
32 OwnedMessage::Error(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?, | |
33 OwnedMessage::Info(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?, | |
34 OwnedMessage::RadioPrompt(p) => TextAnswer::fill(output, &(p.answer()?))?, | |
35 } | |
36 } | |
37 Ok(outputs) | |
38 } | |
39 | |
40 /// Converts this into a `*Answer` for passing to PAM. | |
41 /// | |
42 /// This object is consumed and the `Answer` pointer now owns its data. | |
43 /// It can be recreated with [`Self::from_c_heap`]. | |
44 pub fn into_ptr(self) -> *mut Answer { | |
45 let ret = self.base; | |
46 mem::forget(self); | |
47 ret | |
48 } | |
49 | |
50 /// Takes ownership of a list of answers allocated on the C heap. | |
51 /// | |
52 /// # Safety | |
53 /// | |
54 /// It's up to you to make sure you pass a valid pointer, | |
55 /// like one that you got from PAM, or maybe [`Self::into_ptr`]. | |
56 pub unsafe fn from_c_heap(base: *mut Answer, count: usize) -> Self { | |
57 Answers { base, count } | |
58 } | |
59 } | |
60 | |
61 impl Deref for Answers { | |
62 type Target = [Answer]; | |
63 fn deref(&self) -> &Self::Target { | |
64 // SAFETY: This is the memory we manage ourselves. | |
65 unsafe { slice::from_raw_parts(self.base, self.count) } | |
66 } | |
67 } | |
68 | |
69 impl DerefMut for Answers { | |
70 fn deref_mut(&mut self) -> &mut Self::Target { | |
71 // SAFETY: This is the memory we manage ourselves. | |
72 unsafe { slice::from_raw_parts_mut(self.base, self.count) } | |
73 } | |
74 } | |
75 | |
76 impl Drop for Answers { | |
77 fn drop(&mut self) { | |
78 // SAFETY: We allocated this ourselves, or it was provided to us by PAM. | |
79 unsafe { | |
80 for answer in self.iter_mut() { | |
81 answer.free_contents() | |
82 } | |
83 memory::free(self.base) | |
84 } | |
85 } | |
86 } | |
87 | |
88 #[repr(transparent)] | |
89 #[derive(Debug)] | |
90 pub struct TextAnswer(Answer); | |
91 | |
92 impl TextAnswer { | |
93 /// Interprets the provided `Answer` as a text answer. | |
94 /// | |
95 /// # Safety | |
96 /// | |
97 /// It's up to you to provide an answer that is a `TextAnswer`. | |
98 pub unsafe fn upcast(from: &mut Answer) -> &mut Self { | |
99 // SAFETY: We're provided a valid reference. | |
100 &mut *(from as *mut Answer).cast::<Self>() | |
101 } | |
102 | |
103 /// Converts the `Answer` to a `TextAnswer` with the given text. | |
104 fn fill(dest: &mut Answer, text: &str) -> Result<()> { | |
105 let allocated = memory::malloc_str(text)?; | |
106 dest.free_contents(); | |
107 dest.data = allocated.cast(); | |
108 Ok(()) | |
109 } | |
110 | |
111 /// Gets the string stored in this answer. | |
112 pub fn contents(&self) -> Result<&str> { | |
113 if self.0.data.is_null() { | |
114 Ok("") | |
115 } else { | |
116 // SAFETY: This data is either passed from PAM (so we are forced | |
117 // to trust it) or was created by us in TextAnswerInner::alloc. | |
118 // In either case, it's going to be a valid null-terminated string. | |
119 unsafe { CStr::from_ptr(self.0.data.cast()) } | |
120 .to_str() | |
121 .map_err(|_| ErrorCode::ConversationError) | |
122 } | |
123 } | |
124 | |
125 /// Zeroes out the answer data, frees it, and points our data to `null`. | |
126 /// | |
127 /// When this `TextAnswer` is part of an [`Answers`], | |
128 /// this is optional (since that will perform the `free`), | |
129 /// but it will clear potentially sensitive data. | |
130 pub fn free_contents(&mut self) { | |
131 // SAFETY: We own this data and know it's valid. | |
132 // If it's null, this is a no-op. | |
133 // After we're done, it will be null. | |
134 unsafe { | |
135 memory::zero_c_string(self.0.data.cast()); | |
136 memory::free(self.0.data); | |
137 self.0.data = ptr::null_mut() | |
138 } | |
139 } | |
140 } | |
141 | |
142 /// A [`Answer`] with [`CBinaryData`] in it. | |
143 #[repr(transparent)] | |
144 #[derive(Debug)] | |
145 pub struct BinaryAnswer(Answer); | |
146 | |
147 impl BinaryAnswer { | |
148 /// Interprets the provided [`Answer`] as a binary answer. | |
149 /// | |
150 /// # Safety | |
151 /// | |
152 /// It's up to you to provide an answer that is a `BinaryAnswer`. | |
153 pub unsafe fn upcast(from: &mut Answer) -> &mut Self { | |
154 // SAFETY: We're provided a valid reference. | |
155 &mut *(from as *mut Answer).cast::<Self>() | |
156 } | |
157 | |
158 /// Fills in a [`Answer`] with the provided binary data. | |
159 /// | |
160 /// The `data_type` is a tag you can use for whatever. | |
161 /// It is passed through PAM unchanged. | |
162 /// | |
163 /// The referenced data is copied to the C heap. | |
164 /// We do not take ownership of the original data. | |
165 pub fn fill(dest: &mut Answer, data_and_type: (&[u8], u8)) -> Result<()> { | |
166 let allocated = CBinaryData::alloc(data_and_type)?; | |
167 dest.free_contents(); | |
168 dest.data = allocated.cast(); | |
169 Ok(()) | |
170 } | |
171 | |
172 /// Gets the binary data in this answer. | |
173 pub fn data(&self) -> Option<&CBinaryData> { | |
174 // SAFETY: We either got this data from PAM or allocated it ourselves. | |
175 // Either way, we trust that it is either valid data or null. | |
176 unsafe { self.0.data.cast::<CBinaryData>().as_ref() } | |
177 } | |
178 | |
179 /// Zeroes out the answer data, frees it, and points our data to `null`. | |
180 /// | |
181 /// When this `TextAnswer` is part of an [`Answers`], | |
182 /// this is optional (since that will perform the `free`), | |
183 /// but it will clear potentially sensitive data. | |
184 pub fn zero_contents(&mut self) { | |
185 // SAFETY: We know that our data pointer is either valid or null. | |
186 // Once we're done, it's null and the answer is safe. | |
187 unsafe { | |
188 let data_ref = self.0.data.cast::<CBinaryData>().as_mut(); | |
189 if let Some(d) = data_ref { | |
190 d.zero_contents() | |
191 } | |
192 memory::free(self.0.data); | |
193 self.0.data = ptr::null_mut() | |
194 } | |
195 } | |
196 } | |
197 | |
198 /// Generic version of answer data. | |
199 /// | |
200 /// This has the same structure as [`BinaryAnswer`] | |
201 /// and [`TextAnswer`]. | |
202 #[repr(C)] | |
203 #[derive(Debug)] | |
204 pub struct Answer { | |
205 /// Pointer to the data returned in an answer. | |
206 /// For most answers, this will be a [`CStr`], but for answers to | |
207 /// [`MessageStyle::BinaryPrompt`]s, this will be [`CBinaryData`] | |
208 /// (a Linux-PAM extension). | |
209 data: *mut c_void, | |
210 /// Unused. | |
211 return_code: c_int, | |
212 _marker: Immovable, | |
213 } | |
214 | |
215 impl Answer { | |
216 /// Frees the contents of this answer. | |
217 /// | |
218 /// After this is done, this answer's `data` will be `null`, | |
219 /// which is a valid (empty) state. | |
220 fn free_contents(&mut self) { | |
221 // SAFETY: We have either an owned valid pointer, or null. | |
222 // We can free our owned pointer, and `free(null)` is a no-op. | |
223 unsafe { | |
224 memory::free(self.data); | |
225 self.data = ptr::null_mut(); | |
226 } | |
227 } | |
228 } | |
229 | |
230 #[cfg(test)] | |
231 mod tests { | |
232 use super::{Answer, Answers, BinaryAnswer, TextAnswer}; | |
233 use crate::conv::{BinaryQAndA, ErrorMsg, InfoMsg, MaskedQAndA, QAndA, RadioQAndA}; | |
234 use crate::libpam::conversation::OwnedMessage; | |
235 use crate::BinaryData; | |
236 use crate::libpam::memory; | |
237 | |
238 #[test] | |
239 fn test_round_trip() { | |
240 let binary_msg = { | |
241 let qa = BinaryQAndA::new((&[][..], 0)); | |
242 qa.set_answer(Ok(BinaryData::new(vec![1, 2, 3], 99))); | |
243 OwnedMessage::BinaryPrompt(qa) | |
244 }; | |
245 | |
246 macro_rules! answered { | |
247 ($typ:ty, $msg:path, $data:expr) => {{ | |
248 let qa = <$typ>::new(""); | |
249 qa.set_answer(Ok($data)); | |
250 $msg(qa) | |
251 }}; | |
252 } | |
253 | |
254 let answers = vec![ | |
255 binary_msg, | |
256 answered!(QAndA, OwnedMessage::Prompt, "whats going on".to_owned()), | |
257 answered!(MaskedQAndA, OwnedMessage::MaskedPrompt, "well then".into()), | |
258 answered!(ErrorMsg, OwnedMessage::Error, ()), | |
259 answered!(InfoMsg, OwnedMessage::Info, ()), | |
260 answered!( | |
261 RadioQAndA, | |
262 OwnedMessage::RadioPrompt, | |
263 "beep boop".to_owned() | |
264 ), | |
265 ]; | |
266 let n = answers.len(); | |
267 let sent = Answers::build(answers).unwrap(); | |
268 let heap_answers = sent.into_ptr(); | |
269 let mut received = unsafe { Answers::from_c_heap(heap_answers, n) }; | |
270 | |
271 let assert_text = |want, raw| { | |
272 let up = unsafe { TextAnswer::upcast(raw) }; | |
273 assert_eq!(want, up.contents().unwrap()); | |
274 up.free_contents(); | |
275 assert_eq!("", up.contents().unwrap()); | |
276 }; | |
277 let assert_bin = |want, raw| { | |
278 let up = unsafe { BinaryAnswer::upcast(raw) }; | |
279 assert_eq!(BinaryData::from(want), up.data().into()); | |
280 up.zero_contents(); | |
281 assert_eq!(BinaryData::default(), up.data().into()); | |
282 }; | |
283 if let [zero, one, two, three, four, five] = &mut received[..] { | |
284 assert_bin((&[1, 2, 3][..], 99), zero); | |
285 assert_text("whats going on", one); | |
286 assert_text("well then", two); | |
287 assert_text("", three); | |
288 assert_text("", four); | |
289 assert_text("beep boop", five); | |
290 } else { | |
291 panic!("received wrong size {len}!", len = received.len()) | |
292 } | |
293 } | |
294 | |
295 #[test] | |
296 fn test_text_answer() { | |
297 let answer_ptr: *mut Answer = memory::calloc(1); | |
298 let answer = unsafe {&mut *answer_ptr}; | |
299 TextAnswer::fill(answer, "hello").unwrap(); | |
300 let zeroth_text = unsafe { TextAnswer::upcast(answer) }; | |
301 let data = zeroth_text.contents().expect("valid"); | |
302 assert_eq!("hello", data); | |
303 zeroth_text.free_contents(); | |
304 zeroth_text.free_contents(); | |
305 TextAnswer::fill(answer, "hell\0").expect_err("should error; contains nul"); | |
306 unsafe { memory::free(answer_ptr) } | |
307 } | |
308 | |
309 #[test] | |
310 fn test_binary_answer() { | |
311 let answer_ptr: *mut Answer = memory::calloc(1); | |
312 let answer = unsafe { &mut *answer_ptr }; | |
313 let real_data = BinaryData::new(vec![1, 2, 3, 4, 5, 6, 7, 8], 9); | |
314 BinaryAnswer::fill(answer, (&real_data).into()).expect("alloc should succeed"); | |
315 let bin_answer = unsafe { BinaryAnswer::upcast(answer) }; | |
316 assert_eq!(real_data, bin_answer.data().into()); | |
317 answer.free_contents(); | |
318 answer.free_contents(); | |
319 unsafe { memory::free(answer_ptr) } | |
320 } | |
321 | |
322 #[test] | |
323 #[ignore] | |
324 fn test_binary_answer_too_big() { | |
325 let big_data: Vec<u8> = vec![0xFFu8; 10_000_000_000]; | |
326 let answer_ptr: *mut Answer = memory::calloc(1); | |
327 let answer = unsafe {&mut*answer_ptr}; | |
328 BinaryAnswer::fill(answer, (&big_data, 100)) | |
329 .expect_err("this is too big!"); | |
330 answer.free_contents(); | |
331 unsafe { memory::free(answer) } | |
332 } | |
333 } |