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;