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; |
