comparison src/libpam/question.rs @ 80:5aa1a010f1e8

Start using PAM headers; improve owned/borrowed distinction. - Uses bindgen to generate bindings (only if needed). - Gets the story together on owned vs. borrowed handles. - Reduces number of mutable borrows in handle operation (since `PamHandle` is neither `Send` nor `Sync`, we never have to worry about thread safety. - Improves a bunch of macros so we don't have our own special syntax for docs. - Implement question indirection for standard XSSO PAM implementations.
author Paul Fisher <paul@pfish.zone>
date Tue, 10 Jun 2025 01:09:30 -0400
parents 2128123b9406
children 5e14bb093851
comparison
equal deleted inserted replaced
79:2128123b9406 80:5aa1a010f1e8
1 //! Data and types dealing with PAM messages. 1 //! Data and types dealing with PAM messages.
2 2
3 use crate::constants::InvalidEnum;
4 use crate::conv::{BinaryQAndA, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA, RadioQAndA}; 3 use crate::conv::{BinaryQAndA, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA, RadioQAndA};
5 use crate::libpam::conversation::OwnedMessage; 4 use crate::libpam::conversation::OwnedMessage;
6 use crate::libpam::memory; 5 use crate::libpam::memory;
7 use crate::libpam::memory::{CBinaryData, Immovable}; 6 use crate::libpam::memory::{CBinaryData, Immovable};
7 pub use crate::libpam::pam_ffi::{Question, Style};
8 use crate::ErrorCode; 8 use crate::ErrorCode;
9 use crate::Result; 9 use crate::Result;
10 use num_derive::FromPrimitive; 10 use std::ffi::{c_void, CStr};
11 use num_traits::FromPrimitive;
12 use std::ffi::{c_int, c_void, CStr};
13 use std::result::Result as StdResult;
14 use std::{iter, ptr, slice}; 11 use std::{iter, ptr, slice};
15 12
16 /// Abstraction of a collection of questions to be sent in a PAM conversation. 13 /// Abstraction of a collection of questions to be sent in a PAM conversation.
17 /// 14 ///
18 /// The PAM C API conversation function looks like this: 15 /// The PAM C API conversation function looks like this:
55 /// ║ style ║ 52 /// ║ style ║
56 /// ║ data ┄┄┄┄┄┄┄┄╫┄┄> ... 53 /// ║ data ┄┄┄┄┄┄┄┄╫┄┄> ...
57 /// ╟──────────────╢ 54 /// ╟──────────────╢
58 /// ║ ... ║ 55 /// ║ ... ║
59 /// ``` 56 /// ```
60 /// 57 pub struct GenericQuestions<I: IndirectTrait> {
61 /// ***THIS LIBRARY CURRENTLY SUPPORTS ONLY LINUX-PAM.***
62 pub struct Questions {
63 /// An indirection to the questions themselves, stored on the C heap. 58 /// An indirection to the questions themselves, stored on the C heap.
64 indirect: *mut Indirect, 59 indirect: *mut I,
65 /// The number of questions. 60 /// The number of questions.
66 count: usize, 61 count: usize,
67 } 62 }
68 63
69 impl Questions { 64 impl<I: IndirectTrait> GenericQuestions<I> {
70 /// Stores the provided questions on the C heap. 65 /// Stores the provided questions on the C heap.
71 pub fn new(messages: &[Message]) -> Result<Self> { 66 pub fn new(messages: &[Message]) -> Result<Self> {
72 let count = messages.len(); 67 let count = messages.len();
73 let mut ret = Self { 68 let mut ret = Self {
74 indirect: Indirect::alloc(count), 69 indirect: I::alloc(count),
75 count, 70 count,
76 }; 71 };
77 // Even if we fail partway through this, all our memory will be freed. 72 // Even if we fail partway through this, all our memory will be freed.
78 for (question, message) in iter::zip(ret.iter_mut(), messages) { 73 for (question, message) in iter::zip(ret.iter_mut(), messages) {
79 question.fill(message)? 74 question.fill(message)?
80 } 75 }
81 Ok(ret) 76 Ok(ret)
82 } 77 }
83 78
84 /// The pointer to the thing with the actual list. 79 /// The pointer to the thing with the actual list.
85 pub fn indirect(&self) -> *const Indirect { 80 pub fn indirect(&self) -> *const *const Question {
86 self.indirect 81 self.indirect.cast()
87 } 82 }
88 83
89 pub fn iter(&self) -> impl Iterator<Item = &Question> { 84 pub fn iter(&self) -> impl Iterator<Item = &Question> {
90 // SAFETY: we're iterating over an amount we know. 85 // SAFETY: we're iterating over an amount we know.
91 unsafe { (*self.indirect).iter(self.count) } 86 unsafe { (*self.indirect).iter(self.count) }
94 // SAFETY: we're iterating over an amount we know. 89 // SAFETY: we're iterating over an amount we know.
95 unsafe { (*self.indirect).iter_mut(self.count) } 90 unsafe { (*self.indirect).iter_mut(self.count) }
96 } 91 }
97 } 92 }
98 93
99 impl Drop for Questions { 94 impl<I: IndirectTrait> Drop for GenericQuestions<I> {
100 fn drop(&mut self) { 95 fn drop(&mut self) {
101 // SAFETY: We are valid and have a valid pointer. 96 // SAFETY: We are valid and have a valid pointer.
102 // Once we're done, everything will be safe. 97 // Once we're done, everything will be safe.
103 unsafe { 98 unsafe {
104 if let Some(indirect) = self.indirect.as_mut() { 99 if let Some(indirect) = self.indirect.as_mut() {
105 indirect.free(self.count) 100 indirect.free_contents(self.count)
106 } 101 }
107 memory::free(self.indirect); 102 memory::free(self.indirect);
108 self.indirect = ptr::null_mut(); 103 self.indirect = ptr::null_mut();
109 } 104 }
110 } 105 }
111 } 106 }
112 107
108 /// The trait that each of the `Indirect` implementations implement.
109 ///
110 /// Basically a slice but with more meat.
111 pub trait IndirectTrait {
112 /// Converts a pointer into a borrowed `Self`.
113 ///
114 /// # Safety
115 ///
116 /// You have to provide a valid pointer.
117 unsafe fn borrow_ptr<'a>(ptr: *const *const Question) -> Option<&'a Self>
118 where
119 Self: Sized,
120 {
121 ptr.cast::<Self>().as_ref()
122 }
123
124 /// Allocates memory for this indirector and all its members.
125 fn alloc(count: usize) -> *mut Self;
126
127 /// Returns an iterator yielding the given number of messages.
128 ///
129 /// # Safety
130 ///
131 /// You have to provide the right count.
132 unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &Question>;
133
134 /// Returns a mutable iterator yielding the given number of messages.
135 ///
136 /// # Safety
137 ///
138 /// You have to provide the right count.
139 unsafe fn iter_mut(&mut self, count: usize) -> impl Iterator<Item = &mut Question>;
140
141 /// Frees everything this points to.
142 ///
143 /// # Safety
144 ///
145 /// You have to pass the right size.
146 unsafe fn free_contents(&mut self, count: usize);
147 }
148
113 /// An indirect reference to messages. 149 /// An indirect reference to messages.
114 /// 150 ///
115 /// This is kept separate to provide a place where we can separate 151 /// This is kept separate to provide a place where we can separate
116 /// the pointer-to-pointer-to-list from pointer-to-list-of-pointers. 152 /// the pointer-to-pointer-to-list from pointer-to-list-of-pointers.
153 #[cfg(pam_impl = "linux-pam")]
154 pub type Indirect = LinuxPamIndirect;
155
156 /// An indirect reference to messages.
157 ///
158 /// This is kept separate to provide a place where we can separate
159 /// the pointer-to-pointer-to-list from pointer-to-list-of-pointers.
160 #[cfg(not(pam_impl = "linux-pam"))]
161 pub type Indirect = XssoIndirect;
162
163 pub type Questions = GenericQuestions<Indirect>;
164
165 /// The XSSO standard version of the indirection layer between Question and Questions.
117 #[repr(transparent)] 166 #[repr(transparent)]
118 pub struct Indirect { 167 pub struct StandardIndirect {
168 base: *mut Question,
169 _marker: Immovable,
170 }
171
172 impl IndirectTrait for StandardIndirect {
173 fn alloc(count: usize) -> *mut Self {
174 let questions = memory::calloc(count);
175 let me_ptr: *mut Self = memory::calloc(1);
176 // SAFETY: We just allocated this, and we're putting a valid pointer in.
177 unsafe {
178 let me = &mut *me_ptr;
179 me.base = questions;
180 }
181 me_ptr
182 }
183
184 unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &Question> {
185 (0..count).map(|idx| &*self.base.add(idx))
186 }
187
188 unsafe fn iter_mut(&mut self, count: usize) -> impl Iterator<Item = &mut Question> {
189 (0..count).map(|idx| &mut *self.base.add(idx))
190 }
191
192 unsafe fn free_contents(&mut self, count: usize) {
193 let msgs = slice::from_raw_parts_mut(self.base, count);
194 for msg in msgs {
195 msg.clear()
196 }
197 memory::free(self.base);
198 self.base = ptr::null_mut()
199 }
200 }
201
202 /// The Linux version of the indirection layer between Question and Questions.
203 #[repr(transparent)]
204 pub struct LinuxPamIndirect {
119 base: [*mut Question; 0], 205 base: [*mut Question; 0],
120 _marker: Immovable, 206 _marker: Immovable,
121 } 207 }
122 208
123 impl Indirect { 209 impl IndirectTrait for LinuxPamIndirect {
124 /// Allocates memory for this indirector and all its members.
125 fn alloc(count: usize) -> *mut Self { 210 fn alloc(count: usize) -> *mut Self {
126 // SAFETY: We're only allocating, and when we're done, 211 // SAFETY: We're only allocating, and when we're done,
127 // everything will be in a known-good state. 212 // everything will be in a known-good state.
128 let me_ptr: *mut Indirect = memory::calloc::<Question>(count).cast(); 213 let me_ptr: *mut Self = memory::calloc::<*mut Question>(count).cast();
129 unsafe { 214 unsafe {
130 let me = &mut *me_ptr; 215 let me = &mut *me_ptr;
131 let ptr_list = slice::from_raw_parts_mut(me.base.as_mut_ptr(), count); 216 let ptr_list = slice::from_raw_parts_mut(me.base.as_mut_ptr(), count);
132 for entry in ptr_list { 217 for entry in ptr_list {
133 *entry = memory::calloc(1); 218 *entry = memory::calloc(1);
134 } 219 }
135 me 220 }
136 } 221 me_ptr
137 } 222 }
138 223
139 /// Returns an iterator yielding the given number of messages. 224 unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &Question> {
140 ///
141 /// # Safety
142 ///
143 /// You have to provide the right count.
144 pub unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &Question> {
145 (0..count).map(|idx| &**self.base.as_ptr().add(idx)) 225 (0..count).map(|idx| &**self.base.as_ptr().add(idx))
146 } 226 }
147 227
148 /// Returns a mutable iterator yielding the given number of messages. 228 unsafe fn iter_mut(&mut self, count: usize) -> impl Iterator<Item = &mut Question> {
149 ///
150 /// # Safety
151 ///
152 /// You have to provide the right count.
153 pub unsafe fn iter_mut(&mut self, count: usize) -> impl Iterator<Item = &mut Question> {
154 (0..count).map(|idx| &mut **self.base.as_mut_ptr().add(idx)) 229 (0..count).map(|idx| &mut **self.base.as_mut_ptr().add(idx))
155 } 230 }
156 231
157 /// Frees everything this points to. 232 unsafe fn free_contents(&mut self, count: usize) {
158 ///
159 /// # Safety
160 ///
161 /// You have to pass the right size.
162 unsafe fn free(&mut self, count: usize) {
163 let msgs = slice::from_raw_parts_mut(self.base.as_mut_ptr(), count); 233 let msgs = slice::from_raw_parts_mut(self.base.as_mut_ptr(), count);
164 for msg in msgs { 234 for msg in msgs {
165 if let Some(msg) = msg.as_mut() { 235 if let Some(msg) = msg.as_mut() {
166 msg.clear(); 236 msg.clear();
167 } 237 }
169 *msg = ptr::null_mut(); 239 *msg = ptr::null_mut();
170 } 240 }
171 } 241 }
172 } 242 }
173 243
174 /// The C enum values for messages shown to the user. 244 impl Default for Question {
175 #[derive(Debug, PartialEq, FromPrimitive)] 245 fn default() -> Self {
176 pub enum Style { 246 Self {
177 /// Requests information from the user; will be masked when typing. 247 style: Default::default(),
178 PromptEchoOff = 1, 248 data: ptr::null_mut(),
179 /// Requests information from the user; will not be masked. 249 _marker: Default::default(),
180 PromptEchoOn = 2, 250 }
181 /// An error message. 251 }
182 ErrorMsg = 3,
183 /// An informational message.
184 TextInfo = 4,
185 /// Yes/No/Maybe conditionals. A Linux-PAM extension.
186 RadioType = 5,
187 /// For server–client non-human interaction.
188 ///
189 /// NOT part of the X/Open PAM specification.
190 /// A Linux-PAM extension.
191 BinaryPrompt = 7,
192 }
193
194 impl TryFrom<c_int> for Style {
195 type Error = InvalidEnum<Self>;
196 fn try_from(value: c_int) -> StdResult<Self, Self::Error> {
197 Self::from_i32(value).ok_or(value.into())
198 }
199 }
200
201 impl From<Style> for c_int {
202 fn from(val: Style) -> Self {
203 val as Self
204 }
205 }
206
207 /// A question sent by PAM or a module to an application.
208 ///
209 /// PAM refers to this as a "message", but we call it a question
210 /// to avoid confusion with [`Message`].
211 ///
212 /// This question, and its internal data, is owned by its creator
213 /// (either the module or PAM itself).
214 #[repr(C)]
215 pub struct Question {
216 /// The style of message to request.
217 style: c_int,
218 /// A description of the data requested.
219 ///
220 /// For most requests, this will be an owned [`CStr`], but for requests
221 /// with [`Style::BinaryPrompt`], this will be [`CBinaryData`]
222 /// (a Linux-PAM extension).
223 data: *mut c_void,
224 _marker: Immovable,
225 } 252 }
226 253
227 impl Question { 254 impl Question {
228 /// Replaces the contents of this question with the question 255 /// Replaces the contents of this question with the question
229 /// from the message. 256 /// from the message.
230 pub fn fill(&mut self, msg: &Message) -> Result<()> { 257 pub fn fill(&mut self, msg: &Message) -> Result<()> {
231 let (style, data) = copy_to_heap(msg)?; 258 let (style, data) = copy_to_heap(msg)?;
232 self.clear(); 259 self.clear();
233 self.style = style as c_int; 260 self.style = style.into();
234 self.data = data; 261 self.data = data;
235 Ok(()) 262 Ok(())
236 } 263 }
237 264
238 /// Gets this message's data pointer as a string. 265 /// Gets this message's data pointer as a string.
325 } 352 }
326 353
327 #[cfg(test)] 354 #[cfg(test)]
328 mod tests { 355 mod tests {
329 356
330 use super::{MaskedQAndA, Questions, Result}; 357 use super::{
331 use crate::conv::{BinaryQAndA, ErrorMsg, InfoMsg, QAndA, RadioQAndA}; 358 BinaryQAndA, ErrorMsg, GenericQuestions, IndirectTrait, InfoMsg, LinuxPamIndirect,
332 use crate::libpam::conversation::OwnedMessage; 359 MaskedQAndA, OwnedMessage, QAndA, RadioQAndA, Result, StandardIndirect,
333 360 };
334 #[test] 361
335 fn test_round_trip() { 362 macro_rules! assert_matches {
336 let interrogation = Questions::new(&[ 363 ($id:ident => $variant:path, $q:expr) => {
337 MaskedQAndA::new("hocus pocus").message(), 364 if let $variant($id) = $id {
338 BinaryQAndA::new((&[5, 4, 3, 2, 1], 66)).message(), 365 assert_eq!($q, $id.question());
339 QAndA::new("what").message(), 366 } else {
340 QAndA::new("who").message(), 367 panic!("mismatched enum variant {x:?}", x = $id);
341 InfoMsg::new("hey").message(), 368 }
342 ErrorMsg::new("gasp").message(), 369 };
343 RadioQAndA::new("you must choose").message(), 370 }
344 ]) 371
345 .unwrap(); 372 macro_rules! tests { ($fn_name:ident<$typ:ident>) => {
346 let indirect = interrogation.indirect(); 373 #[test]
347 374 fn $fn_name() {
348 let remade = unsafe { indirect.as_ref() }.unwrap(); 375 let interrogation = GenericQuestions::<$typ>::new(&[
349 let messages: Vec<OwnedMessage> = unsafe { remade.iter(interrogation.count) } 376 MaskedQAndA::new("hocus pocus").message(),
350 .map(TryInto::try_into) 377 BinaryQAndA::new((&[5, 4, 3, 2, 1], 66)).message(),
351 .collect::<Result<_>>() 378 QAndA::new("what").message(),
379 QAndA::new("who").message(),
380 InfoMsg::new("hey").message(),
381 ErrorMsg::new("gasp").message(),
382 RadioQAndA::new("you must choose").message(),
383 ])
352 .unwrap(); 384 .unwrap();
353 let [masked, bin, what, who, hey, gasp, choose] = messages.try_into().unwrap(); 385 let indirect = interrogation.indirect();
354 macro_rules! assert_matches { 386
355 ($id:ident => $variant:path, $q:expr) => { 387 let remade = unsafe { $typ::borrow_ptr(indirect) }.unwrap();
356 if let $variant($id) = $id { 388 let messages: Vec<OwnedMessage> = unsafe { remade.iter(interrogation.count) }
357 assert_eq!($q, $id.question()); 389 .map(TryInto::try_into)
358 } else { 390 .collect::<Result<_>>()
359 panic!("mismatched enum variant {x:?}", x = $id); 391 .unwrap();
360 } 392 let [masked, bin, what, who, hey, gasp, choose] = messages.try_into().unwrap();
361 }; 393 assert_matches!(masked => OwnedMessage::MaskedPrompt, "hocus pocus");
362 } 394 assert_matches!(bin => OwnedMessage::BinaryPrompt, (&[5, 4, 3, 2, 1][..], 66));
363 assert_matches!(masked => OwnedMessage::MaskedPrompt, "hocus pocus"); 395 assert_matches!(what => OwnedMessage::Prompt, "what");
364 assert_matches!(bin => OwnedMessage::BinaryPrompt, (&[5, 4, 3, 2, 1][..], 66)); 396 assert_matches!(who => OwnedMessage::Prompt, "who");
365 assert_matches!(what => OwnedMessage::Prompt, "what"); 397 assert_matches!(hey => OwnedMessage::Info, "hey");
366 assert_matches!(who => OwnedMessage::Prompt, "who"); 398 assert_matches!(gasp => OwnedMessage::Error, "gasp");
367 assert_matches!(hey => OwnedMessage::Info, "hey"); 399 assert_matches!(choose => OwnedMessage::RadioPrompt, "you must choose");
368 assert_matches!(gasp => OwnedMessage::Error, "gasp"); 400 }
369 assert_matches!(choose => OwnedMessage::RadioPrompt, "you must choose"); 401 }}
370 } 402
371 } 403 tests!(test_xsso<StandardIndirect>);
404 tests!(test_linux<LinuxPamIndirect>);
405 }