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