Mercurial > crates > nonstick
comparison src/libpam/question.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 | a508a69c068a |
| children | 4b3a5095f68c |
comparison
equal
deleted
inserted
replaced
| 142:5c1e315c18ff | 143:ebb71a412b58 |
|---|---|
| 3 use crate::conv::{ErrorMsg, Exchange, InfoMsg, MaskedQAndA, QAndA}; | 3 use crate::conv::{ErrorMsg, Exchange, InfoMsg, MaskedQAndA, QAndA}; |
| 4 use crate::libpam::conversation::OwnedExchange; | 4 use crate::libpam::conversation::OwnedExchange; |
| 5 use crate::libpam::memory; | 5 use crate::libpam::memory; |
| 6 use crate::ErrorCode; | 6 use crate::ErrorCode; |
| 7 use crate::Result; | 7 use crate::Result; |
| 8 use libpam_sys_helpers::memory as pammem; | 8 use libpam_sys_helpers::memory as pam_mem; |
| 9 use num_enum::{IntoPrimitive, TryFromPrimitive}; | 9 use num_enum::{IntoPrimitive, TryFromPrimitive}; |
| 10 use std::ffi::{c_int, c_void, CStr}; | 10 use std::ffi::{c_int, c_void, CStr, OsStr}; |
| 11 use std::os::unix::ffi::OsStrExt; | |
| 11 use std::ptr::NonNull; | 12 use std::ptr::NonNull; |
| 12 | 13 |
| 13 mod style_const { | 14 mod style_const { |
| 14 pub use libpam_sys::*; | 15 pub use libpam_sys::*; |
| 15 #[cfg(not(feature = "link"))] | 16 #[cfg(not(feature = "link"))] |
| 67 /// Gets this message's data pointer as a string. | 68 /// Gets this message's data pointer as a string. |
| 68 /// | 69 /// |
| 69 /// # Safety | 70 /// # Safety |
| 70 /// | 71 /// |
| 71 /// It's up to you to pass this only on types with a string value. | 72 /// It's up to you to pass this only on types with a string value. |
| 72 unsafe fn string_data(&self) -> Result<&str> { | 73 unsafe fn string_data(&self) -> &OsStr { |
| 73 match self.data.as_ref() { | 74 match self.data.as_ref() { |
| 74 None => Ok(""), | 75 None => "".as_ref(), |
| 75 Some(data) => CStr::from_ptr(data.as_ptr().cast()) | 76 Some(data) => OsStr::from_bytes(CStr::from_ptr(data.as_ptr().cast()).to_bytes()), |
| 76 .to_str() | |
| 77 .map_err(|_| ErrorCode::ConversationError), | |
| 78 } | 77 } |
| 79 } | 78 } |
| 80 | 79 |
| 81 /// Gets this message's data pointer as borrowed binary data. | 80 /// Gets this message's data pointer as borrowed binary data. |
| 82 unsafe fn binary_data(&self) -> (&[u8], u8) { | 81 unsafe fn binary_data(&self) -> (&[u8], u8) { |
| 83 self.data | 82 self.data |
| 84 .as_ref() | 83 .as_ref() |
| 85 .map(|data| pammem::BinaryPayload::contents(data.as_ptr().cast())) | 84 .map(|data| pam_mem::BinaryPayload::contents(data.as_ptr().cast())) |
| 86 .unwrap_or_default() | 85 .unwrap_or_default() |
| 87 } | 86 } |
| 88 } | 87 } |
| 89 | 88 |
| 90 impl TryFrom<&Exchange<'_>> for Question { | 89 impl TryFrom<&Exchange<'_>> for Question { |
| 91 type Error = ErrorCode; | 90 type Error = ErrorCode; |
| 92 fn try_from(msg: &Exchange) -> Result<Self> { | 91 fn try_from(msg: &Exchange) -> Result<Self> { |
| 93 let alloc = |style, text| -> Result<_> { | 92 let alloc = |style, text: &OsStr| -> Result<_> { |
| 94 Ok((style, unsafe { | 93 Ok((style, unsafe { |
| 95 memory::CHeapBox::cast(memory::CHeapString::new(text)?.into_box()) | 94 memory::CHeapBox::cast(memory::CHeapString::new(text.as_bytes()).into_box()) |
| 96 })) | 95 })) |
| 97 }; | 96 }; |
| 98 // We will only allocate heap data if we have a valid input. | 97 // We will only allocate heap data if we have a valid input. |
| 99 let (style, data): (_, memory::CHeapBox<c_void>) = match *msg { | 98 let (style, data): (_, memory::CHeapBox<c_void>) = match *msg { |
| 100 Exchange::MaskedPrompt(p) => alloc(Style::PromptEchoOff, p.question()), | 99 Exchange::MaskedPrompt(p) => alloc(Style::PromptEchoOff, p.question()), |
| 134 let _ = match style { | 133 let _ = match style { |
| 135 #[cfg(feature = "linux-pam-ext")] | 134 #[cfg(feature = "linux-pam-ext")] |
| 136 Style::BinaryPrompt => self | 135 Style::BinaryPrompt => self |
| 137 .data | 136 .data |
| 138 .as_mut() | 137 .as_mut() |
| 139 .map(|p| pammem::BinaryPayload::zero(p.as_ptr().cast())), | 138 .map(|p| pam_mem::BinaryPayload::zero(p.as_ptr().cast())), |
| 140 #[cfg(feature = "linux-pam-ext")] | 139 #[cfg(feature = "linux-pam-ext")] |
| 141 Style::RadioType => self | 140 Style::RadioType => self |
| 142 .data | 141 .data |
| 143 .as_mut() | 142 .as_mut() |
| 144 .map(|p| memory::CHeapString::zero(p.cast())), | 143 .map(|p| memory::CHeapString::zero(p.cast())), |
| 166 // SAFETY: In all cases below, we're creating questions based on | 165 // SAFETY: In all cases below, we're creating questions based on |
| 167 // known types that we get from PAM and the inner types it should have. | 166 // known types that we get from PAM and the inner types it should have. |
| 168 let prompt = unsafe { | 167 let prompt = unsafe { |
| 169 match style { | 168 match style { |
| 170 Style::PromptEchoOff => { | 169 Style::PromptEchoOff => { |
| 171 Self::MaskedPrompt(MaskedQAndA::new(question.string_data()?)) | 170 Self::MaskedPrompt(MaskedQAndA::new(question.string_data())) |
| 172 } | 171 } |
| 173 Style::PromptEchoOn => Self::Prompt(QAndA::new(question.string_data()?)), | 172 Style::PromptEchoOn => Self::Prompt(QAndA::new(question.string_data())), |
| 174 Style::ErrorMsg => Self::Error(ErrorMsg::new(question.string_data()?)), | 173 Style::ErrorMsg => Self::Error(ErrorMsg::new(question.string_data())), |
| 175 Style::TextInfo => Self::Info(InfoMsg::new(question.string_data()?)), | 174 Style::TextInfo => Self::Info(InfoMsg::new(question.string_data())), |
| 176 #[cfg(feature = "linux-pam-ext")] | 175 #[cfg(feature = "linux-pam-ext")] |
| 177 Style::RadioType => { | 176 Style::RadioType => { |
| 178 Self::RadioPrompt(crate::conv::RadioQAndA::new(question.string_data()?)) | 177 Self::RadioPrompt(crate::conv::RadioQAndA::new(question.string_data())) |
| 179 } | 178 } |
| 180 #[cfg(feature = "linux-pam-ext")] | 179 #[cfg(feature = "linux-pam-ext")] |
| 181 Style::BinaryPrompt => { | 180 Style::BinaryPrompt => { |
| 182 Self::BinaryPrompt(crate::conv::BinaryQAndA::new(question.binary_data())) | 181 Self::BinaryPrompt(crate::conv::BinaryQAndA::new(question.binary_data())) |
| 183 } | 182 } |
| 186 Ok(prompt) | 185 Ok(prompt) |
| 187 } | 186 } |
| 188 } | 187 } |
| 189 | 188 |
| 190 #[cfg(feature = "linux-pam-ext")] | 189 #[cfg(feature = "linux-pam-ext")] |
| 191 impl From<pammem::TooBigError> for ErrorCode { | 190 impl From<pam_mem::TooBigError> for ErrorCode { |
| 192 fn from(_: pammem::TooBigError) -> Self { | 191 fn from(_: pam_mem::TooBigError) -> Self { |
| 193 ErrorCode::BufferError | 192 ErrorCode::BufferError |
| 194 } | 193 } |
| 195 } | 194 } |
| 196 | 195 |
| 197 #[cfg(test)] | 196 #[cfg(test)] |
| 217 | 216 |
| 218 #[test] | 217 #[test] |
| 219 fn standard() { | 218 fn standard() { |
| 220 assert_matches!( | 219 assert_matches!( |
| 221 (Exchange::MaskedPrompt, "hocus pocus"), | 220 (Exchange::MaskedPrompt, "hocus pocus"), |
| 222 MaskedQAndA::new("hocus pocus") | 221 MaskedQAndA::new("hocus pocus".as_ref()) |
| 223 ); | 222 ); |
| 224 assert_matches!((Exchange::Prompt, "what"), QAndA::new("what")); | 223 assert_matches!((Exchange::Prompt, "what"), QAndA::new("what".as_ref())); |
| 225 assert_matches!((Exchange::Prompt, "who"), QAndA::new("who")); | 224 assert_matches!((Exchange::Prompt, "who"), QAndA::new("who".as_ref())); |
| 226 assert_matches!((Exchange::Info, "hey"), InfoMsg::new("hey")); | 225 assert_matches!((Exchange::Info, "hey"), InfoMsg::new("hey".as_ref())); |
| 227 assert_matches!((Exchange::Error, "gasp"), ErrorMsg::new("gasp")); | 226 assert_matches!((Exchange::Error, "gasp"), ErrorMsg::new("gasp".as_ref())); |
| 228 } | 227 } |
| 229 | 228 |
| 230 #[test] | 229 #[test] |
| 231 #[cfg(feature = "linux-pam-ext")] | 230 #[cfg(feature = "linux-pam-ext")] |
| 232 fn linux_extensions() { | 231 fn linux_extensions() { |
| 235 (Exchange::BinaryPrompt, (&[5, 4, 3, 2, 1][..], 66)), | 234 (Exchange::BinaryPrompt, (&[5, 4, 3, 2, 1][..], 66)), |
| 236 BinaryQAndA::new((&[5, 4, 3, 2, 1], 66)) | 235 BinaryQAndA::new((&[5, 4, 3, 2, 1], 66)) |
| 237 ); | 236 ); |
| 238 assert_matches!( | 237 assert_matches!( |
| 239 (Exchange::RadioPrompt, "you must choose"), | 238 (Exchange::RadioPrompt, "you must choose"), |
| 240 RadioQAndA::new("you must choose") | 239 RadioQAndA::new("you must choose".as_ref()) |
| 241 ); | 240 ); |
| 242 } | 241 } |
| 243 } | 242 } |
