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 }