Mercurial > crates > nonstick
comparison src/libpam/answer.rs @ 143:ebb71a412b58
Turn everything into OsString and Just Walk Out! for strings with nul.
To reduce the hazard surface of the API, this replaces most uses of &str
with &OsStr (and likewise with String/OsString).
Also, I've decided that instead of dealing with callers putting `\0`
in their parameters, I'm going to follow the example of std::env and
Just Walk Out! (i.e., panic!()).
This makes things a lot less annoying for both me and (hopefully) users.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sat, 05 Jul 2025 22:12:46 -0400 |
parents | 33b9622ed6d2 |
children | 8f964b701652 |
comparison
equal
deleted
inserted
replaced
142:5c1e315c18ff | 143:ebb71a412b58 |
---|---|
3 use crate::libpam::conversation::OwnedExchange; | 3 use crate::libpam::conversation::OwnedExchange; |
4 use crate::libpam::memory; | 4 use crate::libpam::memory; |
5 use crate::libpam::memory::{CHeapBox, CHeapPayload, CHeapString, Immovable}; | 5 use crate::libpam::memory::{CHeapBox, CHeapPayload, CHeapString, Immovable}; |
6 use crate::{ErrorCode, Result}; | 6 use crate::{ErrorCode, Result}; |
7 use libpam_sys_helpers::memory::BinaryPayload; | 7 use libpam_sys_helpers::memory::BinaryPayload; |
8 use std::ffi::{c_int, c_void, CStr}; | 8 use std::ffi::{c_int, c_void, CStr, OsStr}; |
9 use std::mem::ManuallyDrop; | 9 use std::mem::ManuallyDrop; |
10 use std::ops::{Deref, DerefMut}; | 10 use std::ops::{Deref, DerefMut}; |
11 use std::os::unix::ffi::OsStrExt; | |
11 use std::ptr::NonNull; | 12 use std::ptr::NonNull; |
12 use std::{iter, ptr, slice}; | 13 use std::{iter, ptr, slice}; |
13 | 14 |
14 /// The corridor via which the answer to Messages navigate through PAM. | 15 /// The corridor via which the answer to Messages navigate through PAM. |
15 #[derive(Debug)] | 16 #[derive(Debug)] |
29 }; | 30 }; |
30 // Even if we fail during this process, we still end up freeing | 31 // Even if we fail during this process, we still end up freeing |
31 // all allocated answer memory. | 32 // all allocated answer memory. |
32 for (input, output) in iter::zip(value, outputs.iter_mut()) { | 33 for (input, output) in iter::zip(value, outputs.iter_mut()) { |
33 match input { | 34 match input { |
34 OwnedExchange::MaskedPrompt(p) => TextAnswer::fill(output, p.answer()?.as_ref())?, | 35 OwnedExchange::MaskedPrompt(p) => TextAnswer::fill(output, &p.answer()?)?, |
35 OwnedExchange::Prompt(p) => TextAnswer::fill(output, &(p.answer()?))?, | 36 OwnedExchange::Prompt(p) => TextAnswer::fill(output, &p.answer()?)?, |
36 OwnedExchange::Error(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?, | 37 OwnedExchange::Error(p) => { |
37 OwnedExchange::Info(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?, | 38 TextAnswer::fill(output, p.answer().map(|_| "".as_ref())?)? |
39 } | |
40 OwnedExchange::Info(p) => { | |
41 TextAnswer::fill(output, p.answer().map(|_| "".as_ref())?)? | |
42 } | |
38 // If we're here, that means that we *got* a Linux-PAM | 43 // If we're here, that means that we *got* a Linux-PAM |
39 // question from PAM, so we're OK to answer it. | 44 // question from PAM, so we're OK to answer it. |
40 OwnedExchange::RadioPrompt(p) => TextAnswer::fill(output, &(p.answer()?))?, | 45 OwnedExchange::RadioPrompt(p) => TextAnswer::fill(output, &(p.answer()?))?, |
41 OwnedExchange::BinaryPrompt(p) => { | 46 OwnedExchange::BinaryPrompt(p) => { |
42 BinaryAnswer::fill(output, (&p.answer()?).into())? | 47 BinaryAnswer::fill(output, (&p.answer()?).into())? |
96 } | 101 } |
97 } | 102 } |
98 | 103 |
99 /// Generic version of answer data. | 104 /// Generic version of answer data. |
100 /// | 105 /// |
101 /// This has the same structure as [`BinaryAnswer`](crate::libpam::answer::BinaryAnswer) | 106 /// This has the same structure as [`BinaryAnswer`] and [`TextAnswer`]. |
102 /// and [`TextAnswer`](crate::libpam::answer::TextAnswer). | |
103 #[repr(C)] | 107 #[repr(C)] |
104 #[derive(Debug, Default)] | 108 #[derive(Debug, Default)] |
105 pub struct Answer { | 109 pub struct Answer { |
106 /// Owned pointer to the data returned in an answer. | 110 /// Owned pointer to the data returned in an answer. |
107 /// For most answers, this will be a | 111 /// For most answers, this will be a |
108 /// [`CHeapString`](crate::libpam::memory::CHeapString), | 112 /// [`CHeapString`](CHeapString), but for |
109 /// but for [`BinaryQAndA`](crate::conv::BinaryQAndA)s | 113 /// [`BinaryQAndA`](crate::conv::BinaryQAndA)s (a Linux-PAM extension), |
110 /// (a Linux-PAM extension), this will be a [`CHeapBox`] of | 114 /// this will be a [`CHeapBox`] of |
111 /// [`CBinaryData`](crate::libpam::memory::CBinaryData). | 115 /// [`CBinaryData`](crate::libpam::memory::CBinaryData). |
112 pub data: Option<CHeapBox<c_void>>, | 116 pub data: Option<CHeapBox<c_void>>, |
113 /// Unused. Just here for the padding. | 117 /// Unused. Just here for the padding. |
114 return_code: c_int, | 118 return_code: c_int, |
115 _marker: Immovable, | 119 _marker: Immovable, |
129 // SAFETY: We're provided a valid reference. | 133 // SAFETY: We're provided a valid reference. |
130 &mut *(from as *mut Answer).cast::<Self>() | 134 &mut *(from as *mut Answer).cast::<Self>() |
131 } | 135 } |
132 | 136 |
133 /// Converts the `Answer` to a `TextAnswer` with the given text. | 137 /// Converts the `Answer` to a `TextAnswer` with the given text. |
134 fn fill(dest: &mut Answer, text: &str) -> Result<()> { | 138 fn fill(dest: &mut Answer, text: &OsStr) -> Result<()> { |
135 let allocated = CHeapString::new(text)?; | 139 let allocated = CHeapString::new(text.as_bytes()); |
136 let _ = dest | 140 let _ = dest |
137 .data | 141 .data |
138 .replace(unsafe { CHeapBox::cast(allocated.into_box()) }); | 142 .replace(unsafe { CHeapBox::cast(allocated.into_box()) }); |
139 Ok(()) | 143 Ok(()) |
140 } | 144 } |
235 use super::*; | 239 use super::*; |
236 use crate::conv::{ErrorMsg, InfoMsg, MaskedQAndA, QAndA}; | 240 use crate::conv::{ErrorMsg, InfoMsg, MaskedQAndA, QAndA}; |
237 | 241 |
238 macro_rules! answered { | 242 macro_rules! answered { |
239 ($typ:ty, $msg:path, $data:expr) => {{ | 243 ($typ:ty, $msg:path, $data:expr) => {{ |
240 let qa = <$typ>::new(""); | 244 let qa = <$typ>::new("".as_ref()); |
241 qa.set_answer(Ok($data)); | 245 qa.set_answer(Ok($data)); |
242 $msg(qa) | 246 $msg(qa) |
243 }}; | 247 }}; |
244 } | 248 } |
245 | 249 |
248 assert_eq!(want, up.contents().unwrap()); | 252 assert_eq!(want, up.contents().unwrap()); |
249 up.zero_contents(); | 253 up.zero_contents(); |
250 assert_eq!("", up.contents().unwrap()); | 254 assert_eq!("", up.contents().unwrap()); |
251 } | 255 } |
252 | 256 |
253 fn round_trip(msgs: Vec<OwnedExchange>) -> Answers { | 257 fn round_trip(exchanges: Vec<OwnedExchange>) -> Answers { |
254 let n = msgs.len(); | 258 let n = exchanges.len(); |
255 let sent = Answers::build(msgs).unwrap(); | 259 let sent = Answers::build(exchanges).unwrap(); |
256 unsafe { Answers::from_c_heap(NonNull::new_unchecked(sent.into_ptr()), n) } | 260 unsafe { Answers::from_c_heap(NonNull::new_unchecked(sent.into_ptr()), n) } |
257 } | 261 } |
258 | 262 |
259 #[test] | 263 #[test] |
260 fn test_round_trip() { | 264 fn test_round_trip() { |
261 let mut answers = round_trip(vec![ | 265 let mut answers = round_trip(vec![ |
262 answered!(QAndA, OwnedExchange::Prompt, "whats going on".to_owned()), | 266 answered!(QAndA, OwnedExchange::Prompt, "whats going on".into()), |
263 answered!(MaskedQAndA, OwnedExchange::MaskedPrompt, "well then".into()), | 267 answered!(MaskedQAndA, OwnedExchange::MaskedPrompt, "well then".into()), |
264 answered!(ErrorMsg, OwnedExchange::Error, ()), | 268 answered!(ErrorMsg, OwnedExchange::Error, ()), |
265 answered!(InfoMsg, OwnedExchange::Info, ()), | 269 answered!(InfoMsg, OwnedExchange::Info, ()), |
266 ]); | 270 ]); |
267 | 271 |
283 qa.set_answer(Ok(BinaryData::new(vec![1, 2, 3], 99))); | 287 qa.set_answer(Ok(BinaryData::new(vec![1, 2, 3], 99))); |
284 OwnedExchange::BinaryPrompt(qa) | 288 OwnedExchange::BinaryPrompt(qa) |
285 }; | 289 }; |
286 let mut answers = round_trip(vec![ | 290 let mut answers = round_trip(vec![ |
287 binary_msg, | 291 binary_msg, |
288 answered!( | 292 answered!(RadioQAndA, OwnedExchange::RadioPrompt, "beep boop".into()), |
289 RadioQAndA, | |
290 OwnedExchange::RadioPrompt, | |
291 "beep boop".to_owned() | |
292 ), | |
293 ]); | 293 ]); |
294 | 294 |
295 if let [bin, radio] = &mut answers[..] { | 295 if let [bin, radio] = &mut answers[..] { |
296 let up = unsafe { BinaryAnswer::upcast(bin) }; | 296 let up = unsafe { BinaryAnswer::upcast(bin) }; |
297 assert_eq!((&[1, 2, 3][..], 99), up.contents().unwrap()); | 297 assert_eq!((&[1, 2, 3][..], 99), up.contents().unwrap()); |
305 } | 305 } |
306 | 306 |
307 #[test] | 307 #[test] |
308 fn test_text_answer() { | 308 fn test_text_answer() { |
309 let mut answer: CHeapBox<Answer> = CHeapBox::default(); | 309 let mut answer: CHeapBox<Answer> = CHeapBox::default(); |
310 TextAnswer::fill(&mut answer, "hello").unwrap(); | 310 TextAnswer::fill(&mut answer, "hello".as_ref()).unwrap(); |
311 let zeroth_text = unsafe { TextAnswer::upcast(&mut answer) }; | 311 let zeroth_text = unsafe { TextAnswer::upcast(&mut answer) }; |
312 let data = zeroth_text.contents().expect("valid"); | 312 let data = zeroth_text.contents().expect("valid"); |
313 assert_eq!("hello", data); | 313 assert_eq!("hello", data); |
314 zeroth_text.zero_contents(); | 314 zeroth_text.zero_contents(); |
315 zeroth_text.zero_contents(); | 315 zeroth_text.zero_contents(); |
316 TextAnswer::fill(&mut answer, "hell\0").expect_err("should error; contains nul"); | 316 } |
317 | |
318 #[test] | |
319 #[should_panic] | |
320 fn test_text_answer_nul() { | |
321 TextAnswer::fill(&mut CHeapBox::default(), "hell\0".as_ref()) | |
322 .expect_err("should error; contains nul"); | |
317 } | 323 } |
318 | 324 |
319 #[test] | 325 #[test] |
320 fn test_binary_answer() { | 326 fn test_binary_answer() { |
321 use crate::conv::BinaryData; | 327 use crate::conv::BinaryData; |