Mercurial > crates > nonstick
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 } |