Mercurial > crates > nonstick
comparison src/libpam/question.rs @ 98:b87100c5eed4
Start on environment variables, and make pointers nicer.
This starts work on the PAM environment handling, and in so doing,
introduces the CHeapBox and CHeapString structs. These are analogous
to Box and CString, but they're located on the C heap rather than
being Rust-managed memory.
This is because environment variables deal with even more pointers
and it turns out we can lose a lot of manual freeing using homemade
smart pointers.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Tue, 24 Jun 2025 04:25:25 -0400 |
parents | efc2b56c8928 |
children | 94b51fa4f797 |
comparison
equal
deleted
inserted
replaced
97:efe2f5f8b5b2 | 98:b87100c5eed4 |
---|---|
2 | 2 |
3 #[cfg(feature = "linux-pam-extensions")] | 3 #[cfg(feature = "linux-pam-extensions")] |
4 use crate::conv::{BinaryQAndA, RadioQAndA}; | 4 use crate::conv::{BinaryQAndA, RadioQAndA}; |
5 use crate::conv::{ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA}; | 5 use crate::conv::{ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA}; |
6 use crate::libpam::conversation::OwnedMessage; | 6 use crate::libpam::conversation::OwnedMessage; |
7 use crate::libpam::memory::{CBinaryData, Immovable}; | 7 use crate::libpam::memory::{CBinaryData, CHeapBox, CHeapString, Immovable}; |
8 use crate::libpam::pam_ffi; | |
8 pub use crate::libpam::pam_ffi::Question; | 9 pub use crate::libpam::pam_ffi::Question; |
9 use crate::libpam::{memory, pam_ffi}; | |
10 use crate::ErrorCode; | 10 use crate::ErrorCode; |
11 use crate::Result; | 11 use crate::Result; |
12 use num_enum::{IntoPrimitive, TryFromPrimitive}; | 12 use num_enum::{IntoPrimitive, TryFromPrimitive}; |
13 use std::ffi::{c_void, CStr}; | 13 use std::ffi::{c_void, CStr}; |
14 use std::ptr::NonNull; | 14 use std::slice; |
15 use std::{ptr, slice}; | |
16 | 15 |
17 /// Abstraction of a collection of questions to be sent in a PAM conversation. | 16 /// Abstraction of a collection of questions to be sent in a PAM conversation. |
18 /// | 17 /// |
19 /// The PAM C API conversation function looks like this: | 18 /// The PAM C API conversation function looks like this: |
20 /// | 19 /// |
202 /// | 201 /// |
203 /// # Safety | 202 /// # Safety |
204 /// | 203 /// |
205 /// It's up to you to pass this only on types with a string value. | 204 /// It's up to you to pass this only on types with a string value. |
206 unsafe fn string_data(&self) -> Result<&str> { | 205 unsafe fn string_data(&self) -> Result<&str> { |
207 if self.data.is_null() { | 206 match self.data.as_ref() { |
208 Ok("") | 207 None => Ok(""), |
209 } else { | 208 Some(data) => CStr::from_ptr(CHeapBox::as_ptr(data).cast().as_ptr()) |
210 CStr::from_ptr(self.data.cast()) | |
211 .to_str() | 209 .to_str() |
212 .map_err(|_| ErrorCode::ConversationError) | 210 .map_err(|_| ErrorCode::ConversationError), |
213 } | 211 } |
214 } | 212 } |
215 | 213 |
216 /// Gets this message's data pointer as borrowed binary data. | 214 /// Gets this message's data pointer as borrowed binary data. |
217 unsafe fn binary_data(&self) -> (&[u8], u8) { | 215 unsafe fn binary_data(&self) -> (&[u8], u8) { |
218 NonNull::new(self.data) | 216 self.data |
219 .map(|nn| nn.cast()) | 217 .as_ref() |
220 .map(|ptr| CBinaryData::data(ptr)) | 218 .map(|data| CBinaryData::data(CHeapBox::as_ptr(data).cast())) |
221 .unwrap_or_default() | 219 .unwrap_or_default() |
222 } | 220 } |
223 } | 221 } |
224 | 222 |
225 impl TryFrom<&Message<'_>> for Question { | 223 impl TryFrom<&Message<'_>> for Question { |
226 type Error = ErrorCode; | 224 type Error = ErrorCode; |
227 fn try_from(msg: &Message) -> Result<Self> { | 225 fn try_from(msg: &Message) -> Result<Self> { |
228 let alloc = |style, text| Ok((style, memory::malloc_str(text)?.cast())); | 226 let alloc = |style, text| -> Result<_> { |
227 Ok((style, unsafe { | |
228 CHeapBox::cast(CHeapString::new(text)?.into_box()) | |
229 })) | |
230 }; | |
229 // We will only allocate heap data if we have a valid input. | 231 // We will only allocate heap data if we have a valid input. |
230 let (style, data): (_, NonNull<c_void>) = match *msg { | 232 let (style, data): (_, CHeapBox<c_void>) = match *msg { |
231 Message::MaskedPrompt(p) => alloc(Style::PromptEchoOff, p.question()), | 233 Message::MaskedPrompt(p) => alloc(Style::PromptEchoOff, p.question()), |
232 Message::Prompt(p) => alloc(Style::PromptEchoOn, p.question()), | 234 Message::Prompt(p) => alloc(Style::PromptEchoOn, p.question()), |
233 Message::Error(p) => alloc(Style::ErrorMsg, p.question()), | 235 Message::Error(p) => alloc(Style::ErrorMsg, p.question()), |
234 Message::Info(p) => alloc(Style::TextInfo, p.question()), | 236 Message::Info(p) => alloc(Style::TextInfo, p.question()), |
235 #[cfg(feature = "linux-pam-extensions")] | 237 #[cfg(feature = "linux-pam-extensions")] |
236 Message::RadioPrompt(p) => alloc(Style::RadioType, p.question()), | 238 Message::RadioPrompt(p) => alloc(Style::RadioType, p.question()), |
237 #[cfg(feature = "linux-pam-extensions")] | 239 #[cfg(feature = "linux-pam-extensions")] |
238 Message::BinaryPrompt(p) => Ok(( | 240 Message::BinaryPrompt(p) => Ok((Style::BinaryPrompt, unsafe { |
239 Style::BinaryPrompt, | 241 CHeapBox::cast(CBinaryData::alloc(p.question())?) |
240 CBinaryData::alloc(p.question())?.cast(), | 242 })), |
241 )), | |
242 #[cfg(not(feature = "linux-pam-extensions"))] | 243 #[cfg(not(feature = "linux-pam-extensions"))] |
243 Message::RadioPrompt(_) | Message::BinaryPrompt(_) => Err(ErrorCode::ConversationError), | 244 Message::RadioPrompt(_) | Message::BinaryPrompt(_) => Err(ErrorCode::ConversationError), |
244 }?; | 245 }?; |
245 Ok(Self { | 246 Ok(Self { |
246 style: style.into(), | 247 style: style.into(), |
247 data: data.as_ptr(), | 248 data: Some(data), |
248 _marker: Default::default(), | 249 _marker: Default::default(), |
249 }) | 250 }) |
250 } | 251 } |
251 } | 252 } |
252 | 253 |
256 // After this function is done, it will be zeroed out. | 257 // After this function is done, it will be zeroed out. |
257 unsafe { | 258 unsafe { |
258 // This is nice-to-have. We'll try to zero out the data | 259 // This is nice-to-have. We'll try to zero out the data |
259 // in the Question. If it's not a supported format, we skip it. | 260 // in the Question. If it's not a supported format, we skip it. |
260 if let Ok(style) = Style::try_from(self.style) { | 261 if let Ok(style) = Style::try_from(self.style) { |
261 match style { | 262 let _ = match style { |
262 #[cfg(feature = "linux-pam-extensions")] | 263 #[cfg(feature = "linux-pam-extensions")] |
263 Style::BinaryPrompt => { | 264 Style::BinaryPrompt => self |
264 if let Some(d) = NonNull::new(self.data) { | 265 .data |
265 CBinaryData::zero_contents(d.cast()) | 266 .as_ref() |
266 } | 267 .map(|p| CBinaryData::zero_contents(CHeapBox::as_ptr(p).cast())), |
267 } | |
268 #[cfg(feature = "linux-pam-extensions")] | 268 #[cfg(feature = "linux-pam-extensions")] |
269 Style::RadioType => memory::zero_c_string(self.data.cast()), | 269 Style::RadioType => self |
270 .data | |
271 .as_ref() | |
272 .map(|p| CHeapString::zero(CHeapBox::as_ptr(p).cast())), | |
270 Style::TextInfo | 273 Style::TextInfo |
271 | Style::ErrorMsg | 274 | Style::ErrorMsg |
272 | Style::PromptEchoOff | 275 | Style::PromptEchoOff |
273 | Style::PromptEchoOn => memory::zero_c_string(self.data.cast()), | 276 | Style::PromptEchoOn => self |
274 } | 277 .data |
278 .as_ref() | |
279 .map(|p| CHeapString::zero(CHeapBox::as_ptr(p).cast())), | |
280 }; | |
275 }; | 281 }; |
276 memory::free(self.data); | |
277 self.data = ptr::null_mut(); | |
278 } | 282 } |
279 } | 283 } |
280 } | 284 } |
281 | 285 |
282 impl<'a> TryFrom<&'a Question> for OwnedMessage<'a> { | 286 impl<'a> TryFrom<&'a Question> for OwnedMessage<'a> { |