Mercurial > crates > nonstick
comparison src/libpam/message.rs @ 77:351bdc13005e
Update the libpam module to work with the new structure.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sun, 08 Jun 2025 01:03:46 -0400 |
parents | c30811b4afae |
children |
comparison
equal
deleted
inserted
replaced
76:e58d24849e82 | 77:351bdc13005e |
---|---|
1 //! Data and types dealing with PAM messages. | 1 //! Data and types dealing with PAM messages. |
2 | 2 |
3 use crate::constants::InvalidEnum; | 3 use crate::constants::InvalidEnum; |
4 use crate::conv::Message; | 4 use crate::libpam::conversation::OwnedMessage; |
5 use crate::libpam::memory; | 5 use crate::libpam::memory; |
6 use crate::libpam::memory::{CBinaryData, Immovable, NulError, TooBigError}; | 6 use crate::libpam::memory::{CBinaryData, Immovable}; |
7 use crate::ErrorCode; | |
8 use crate::Result; | |
7 use num_derive::FromPrimitive; | 9 use num_derive::FromPrimitive; |
8 use num_traits::FromPrimitive; | 10 use num_traits::FromPrimitive; |
9 use std::ffi::{c_int, c_void, CStr}; | 11 use std::ffi::{c_int, c_void, CStr}; |
10 use std::result::Result as StdResult; | 12 use std::result::Result as StdResult; |
11 use std::str::Utf8Error; | |
12 use std::{ptr, slice}; | 13 use std::{ptr, slice}; |
13 | 14 use crate::conv::{BorrowedBinaryData, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA, RadioQAndA}; |
14 #[derive(Debug, thiserror::Error)] | |
15 #[error("error creating PAM message: {0}")] | |
16 pub enum ConversionError { | |
17 InvalidEnum(#[from] InvalidEnum<Style>), | |
18 Utf8Error(#[from] Utf8Error), | |
19 NulError(#[from] NulError), | |
20 TooBigError(#[from] TooBigError), | |
21 } | |
22 | 15 |
23 /// The C enum values for messages shown to the user. | 16 /// The C enum values for messages shown to the user. |
24 #[derive(Debug, PartialEq, FromPrimitive)] | 17 #[derive(Debug, PartialEq, FromPrimitive)] |
25 pub enum Style { | 18 pub enum Style { |
26 /// Requests information from the user; will be masked when typing. | 19 /// Requests information from the user; will be masked when typing. |
51 fn from(val: Style) -> Self { | 44 fn from(val: Style) -> Self { |
52 val as Self | 45 val as Self |
53 } | 46 } |
54 } | 47 } |
55 | 48 |
56 /// A message sent by PAM or a module to an application. | 49 /// A question sent by PAM or a module to an application. |
57 /// This message, and its internal data, is owned by the creator | 50 /// |
51 /// PAM refers to this as a "message", but we call it a question | |
52 /// to avoid confusion with [`Message`]. | |
53 /// | |
54 /// This question, and its internal data, is owned by its creator | |
58 /// (either the module or PAM itself). | 55 /// (either the module or PAM itself). |
59 #[repr(C)] | 56 #[repr(C)] |
60 pub struct RawMessage { | 57 pub struct Question { |
61 /// The style of message to request. | 58 /// The style of message to request. |
62 style: c_int, | 59 style: c_int, |
63 /// A description of the data requested. | 60 /// A description of the data requested. |
64 /// | 61 /// |
65 /// For most requests, this will be an owned [`CStr`], but for requests | 62 /// For most requests, this will be an owned [`CStr`], but for requests |
67 /// (a Linux-PAM extension). | 64 /// (a Linux-PAM extension). |
68 data: *mut c_void, | 65 data: *mut c_void, |
69 _marker: Immovable, | 66 _marker: Immovable, |
70 } | 67 } |
71 | 68 |
72 impl RawMessage { | 69 impl Question { |
73 pub fn set(&mut self, msg: Message) -> StdResult<(), ConversionError> { | 70 pub fn fill(&mut self, msg: &Message) -> Result<()> { |
74 let (style, data) = copy_to_heap(msg)?; | 71 let (style, data) = copy_to_heap(msg)?; |
75 self.clear(); | 72 self.clear(); |
76 // SAFETY: We allocated this ourselves or were given it by PAM. | 73 // SAFETY: We allocated this ourselves or were given it by PAM. |
77 // Otherwise, it's null, but free(null) is fine. | 74 // Otherwise, it's null, but free(null) is fine. |
78 unsafe { libc::free(self.data) }; | 75 unsafe { libc::free(self.data) }; |
84 /// Gets this message's data pointer as a string. | 81 /// Gets this message's data pointer as a string. |
85 /// | 82 /// |
86 /// # Safety | 83 /// # Safety |
87 /// | 84 /// |
88 /// It's up to you to pass this only on types with a string value. | 85 /// It's up to you to pass this only on types with a string value. |
89 unsafe fn string_data(&self) -> StdResult<&str, Utf8Error> { | 86 unsafe fn string_data(&self) -> Result<&str> { |
90 if self.data.is_null() { | 87 if self.data.is_null() { |
91 Ok("") | 88 Ok("") |
92 } else { | 89 } else { |
93 CStr::from_ptr(self.data.cast()).to_str() | 90 CStr::from_ptr(self.data.cast()) |
94 } | 91 .to_str() |
92 .map_err(|_| ErrorCode::ConversationError) | |
93 } | |
94 } | |
95 | |
96 /// Gets this message's data pointer as borrowed binary data. | |
97 unsafe fn binary_data(&self) -> BorrowedBinaryData { | |
98 self.data | |
99 .cast::<CBinaryData>() | |
100 .as_ref() | |
101 .map(Into::into) | |
102 .unwrap_or_default() | |
95 } | 103 } |
96 | 104 |
97 /// Zeroes out the data stored here. | 105 /// Zeroes out the data stored here. |
98 fn clear(&mut self) { | 106 fn clear(&mut self) { |
99 // SAFETY: We either created this data or we got it from PAM. | 107 // SAFETY: We either created this data or we got it from PAM. |
117 self.data = ptr::null_mut(); | 125 self.data = ptr::null_mut(); |
118 } | 126 } |
119 } | 127 } |
120 } | 128 } |
121 | 129 |
130 impl<'a> TryFrom<&'a Question> for OwnedMessage<'a> { | |
131 type Error = ErrorCode; | |
132 fn try_from(question: &'a Question) -> Result<Self> { | |
133 let style: Style = question | |
134 .style | |
135 .try_into() | |
136 .map_err(|_| ErrorCode::ConversationError)?; | |
137 // SAFETY: In all cases below, we're matching the | |
138 let prompt = unsafe { | |
139 match style { | |
140 Style::PromptEchoOff => { | |
141 Self::MaskedPrompt(MaskedQAndA::new(question.string_data()?)) | |
142 } | |
143 Style::PromptEchoOn => Self::Prompt(QAndA::new(question.string_data()?)), | |
144 Style::ErrorMsg => Self::Error(ErrorMsg::new(question.string_data()?)), | |
145 Style::TextInfo => Self::Info(InfoMsg::new(question.string_data()?)), | |
146 Style::RadioType => Self::RadioPrompt(RadioQAndA::new(question.string_data()?)), | |
147 Style::BinaryPrompt => Self::BinaryPrompt(question.binary_data().into()), | |
148 } | |
149 }; | |
150 Ok(prompt) | |
151 } | |
152 } | |
153 | |
122 /// Copies the contents of this message to the C heap. | 154 /// Copies the contents of this message to the C heap. |
123 fn copy_to_heap(msg: Message) -> StdResult<(Style, *mut c_void), ConversionError> { | 155 fn copy_to_heap(msg: &Message) -> Result<(Style, *mut c_void)> { |
124 let alloc = |style, text| Ok((style, memory::malloc_str(text)?.cast())); | 156 let alloc = |style, text| Ok((style, memory::malloc_str(text)?.cast())); |
125 match msg { | 157 match *msg { |
126 Message::MaskedPrompt(text) => alloc(Style::PromptEchoOff, text), | 158 Message::MaskedPrompt(p) => alloc(Style::PromptEchoOff, p.question()), |
127 Message::Prompt(text) => alloc(Style::PromptEchoOn, text), | 159 Message::Prompt(p) => alloc(Style::PromptEchoOn, p.question()), |
128 Message::RadioPrompt(text) => alloc(Style::RadioType, text), | 160 Message::RadioPrompt(p) => alloc(Style::RadioType, p.question()), |
129 Message::ErrorMsg(text) => alloc(Style::ErrorMsg, text), | 161 Message::Error(p) => alloc(Style::ErrorMsg, p.question()), |
130 Message::InfoMsg(text) => alloc(Style::TextInfo, text), | 162 Message::Info(p) => alloc(Style::TextInfo, p.question()), |
131 Message::BinaryPrompt { data, data_type } => Ok(( | 163 Message::BinaryPrompt(p) => { |
132 Style::BinaryPrompt, | 164 let q = p.question(); |
133 (CBinaryData::alloc(data, data_type)?).cast(), | 165 Ok(( |
134 )), | 166 Style::BinaryPrompt, |
135 } | 167 CBinaryData::alloc(q.data(), q.data_type())?.cast(), |
136 } | 168 )) |
137 | 169 } |
138 /// Abstraction of a list-of-messages to be sent in a PAM conversation. | 170 } |
171 } | |
172 | |
173 /// Abstraction of a collection of questions to be sent in a PAM conversation. | |
174 /// | |
175 /// The PAM C API conversation function looks like this: | |
176 /// | |
177 /// ```c | |
178 /// int pam_conv( | |
179 /// int count, | |
180 /// const struct pam_message **questions, | |
181 /// struct pam_response **answers, | |
182 /// void *appdata_ptr, | |
183 /// ) | |
184 /// ``` | |
139 /// | 185 /// |
140 /// On Linux-PAM and other compatible implementations, `messages` | 186 /// On Linux-PAM and other compatible implementations, `messages` |
141 /// is treated as a pointer-to-pointers, like `int argc, char **argv`. | 187 /// is treated as a pointer-to-pointers, like `int argc, char **argv`. |
142 /// (In this situation, the value of `OwnedMessages.indirect` is | 188 /// (In this situation, the value of `OwnedMessages.indirect` is |
143 /// the pointer passed to `pam_conv`.) | 189 /// the pointer passed to `pam_conv`.) |
168 /// ╟─────────────╢ | 214 /// ╟─────────────╢ |
169 /// ║ ... ║ | 215 /// ║ ... ║ |
170 /// ``` | 216 /// ``` |
171 /// | 217 /// |
172 /// ***THIS LIBRARY CURRENTLY SUPPORTS ONLY LINUX-PAM.*** | 218 /// ***THIS LIBRARY CURRENTLY SUPPORTS ONLY LINUX-PAM.*** |
173 pub struct OwnedMessages { | 219 pub struct Questions { |
174 /// An indirection to the messages themselves, stored on the C heap. | 220 /// An indirection to the questions themselves, stored on the C heap. |
175 indirect: *mut MessageIndirector, | 221 indirect: *mut Indirect, |
176 /// The number of messages in the list. | 222 /// The number of questions. |
177 count: usize, | 223 count: usize, |
178 } | 224 } |
179 | 225 |
180 impl OwnedMessages { | 226 impl Questions { |
181 /// Allocates data to store messages on the C heap. | 227 /// Allocates data to store questions on the C heap. |
182 pub fn alloc(count: usize) -> Self { | 228 pub fn alloc(count: usize) -> Self { |
183 Self { | 229 Self { |
184 indirect: MessageIndirector::alloc(count), | 230 indirect: Indirect::alloc(count), |
185 count, | 231 count, |
186 } | 232 } |
187 } | 233 } |
188 | 234 |
189 /// The pointer to the thing with the actual list. | 235 /// The pointer to the thing with the actual list. |
190 pub fn indirector(&self) -> *const MessageIndirector { | 236 pub fn indirect(&self) -> *const Indirect { |
191 self.indirect | 237 self.indirect |
192 } | 238 } |
193 | 239 |
194 pub fn iter(&self) -> impl Iterator<Item = &RawMessage> { | 240 pub fn iter(&self) -> impl Iterator<Item = &Question> { |
195 // SAFETY: we're iterating over an amount we know. | 241 // SAFETY: we're iterating over an amount we know. |
196 unsafe { (*self.indirect).iter(self.count) } | 242 unsafe { (*self.indirect).iter(self.count) } |
197 } | 243 } |
198 | 244 |
199 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut RawMessage> { | 245 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Question> { |
200 // SAFETY: we're iterating over an amount we know. | 246 // SAFETY: we're iterating over an amount we know. |
201 unsafe { (*self.indirect).iter_mut(self.count) } | 247 unsafe { (*self.indirect).iter_mut(self.count) } |
202 } | 248 } |
203 } | 249 } |
204 | 250 |
205 impl Drop for OwnedMessages { | 251 impl Drop for Questions { |
206 fn drop(&mut self) { | 252 fn drop(&mut self) { |
207 // SAFETY: We are valid and have a valid pointer. | 253 // SAFETY: We are valid and have a valid pointer. |
208 // Once we're done, everything will be safe. | 254 // Once we're done, everything will be safe. |
209 unsafe { | 255 unsafe { |
210 if let Some(indirect) = self.indirect.as_mut() { | 256 if let Some(indirect) = self.indirect.as_mut() { |
219 /// An indirect reference to messages. | 265 /// An indirect reference to messages. |
220 /// | 266 /// |
221 /// This is kept separate to provide a place where we can separate | 267 /// This is kept separate to provide a place where we can separate |
222 /// the pointer-to-pointer-to-list from pointer-to-list-of-pointers. | 268 /// the pointer-to-pointer-to-list from pointer-to-list-of-pointers. |
223 #[repr(transparent)] | 269 #[repr(transparent)] |
224 pub struct MessageIndirector { | 270 pub struct Indirect { |
225 base: [*mut RawMessage; 0], | 271 base: [*mut Question; 0], |
226 _marker: Immovable, | 272 _marker: Immovable, |
227 } | 273 } |
228 | 274 |
229 impl MessageIndirector { | 275 impl Indirect { |
230 /// Allocates memory for this indirector and all its members. | 276 /// Allocates memory for this indirector and all its members. |
231 fn alloc(count: usize) -> *mut Self { | 277 fn alloc(count: usize) -> *mut Self { |
232 // SAFETY: We're only allocating, and when we're done, | 278 // SAFETY: We're only allocating, and when we're done, |
233 // everything will be in a known-good state. | 279 // everything will be in a known-good state. |
234 unsafe { | 280 unsafe { |
235 let me_ptr: *mut MessageIndirector = | 281 let me_ptr: *mut Indirect = libc::calloc(count, size_of::<*mut Question>()).cast(); |
236 libc::calloc(count, size_of::<*mut RawMessage>()).cast(); | |
237 let me = &mut *me_ptr; | 282 let me = &mut *me_ptr; |
238 let ptr_list = slice::from_raw_parts_mut(me.base.as_mut_ptr(), count); | 283 let ptr_list = slice::from_raw_parts_mut(me.base.as_mut_ptr(), count); |
239 for entry in ptr_list { | 284 for entry in ptr_list { |
240 *entry = libc::calloc(1, size_of::<RawMessage>()).cast(); | 285 *entry = libc::calloc(1, size_of::<Question>()).cast(); |
241 } | 286 } |
242 me | 287 me |
243 } | 288 } |
244 } | 289 } |
245 | 290 |
246 /// Returns an iterator yielding the given number of messages. | 291 /// Returns an iterator yielding the given number of messages. |
247 /// | 292 /// |
248 /// # Safety | 293 /// # Safety |
249 /// | 294 /// |
250 /// You have to provide the right count. | 295 /// You have to provide the right count. |
251 pub unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &RawMessage> { | 296 pub unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &Question> { |
252 (0..count).map(|idx| &**self.base.as_ptr().add(idx)) | 297 (0..count).map(|idx| &**self.base.as_ptr().add(idx)) |
253 } | 298 } |
254 | 299 |
255 /// Returns a mutable iterator yielding the given number of messages. | 300 /// Returns a mutable iterator yielding the given number of messages. |
256 /// | 301 /// |
257 /// # Safety | 302 /// # Safety |
258 /// | 303 /// |
259 /// You have to provide the right count. | 304 /// You have to provide the right count. |
260 pub unsafe fn iter_mut(&mut self, count: usize) -> impl Iterator<Item = &mut RawMessage> { | 305 pub unsafe fn iter_mut(&mut self, count: usize) -> impl Iterator<Item = &mut Question> { |
261 (0..count).map(|idx| &mut **self.base.as_mut_ptr().add(idx)) | 306 (0..count).map(|idx| &mut **self.base.as_mut_ptr().add(idx)) |
262 } | 307 } |
263 | 308 |
264 /// Frees this and everything it points to. | 309 /// Frees this and everything it points to. |
265 /// | 310 /// |
276 *msg = ptr::null_mut(); | 321 *msg = ptr::null_mut(); |
277 } | 322 } |
278 } | 323 } |
279 } | 324 } |
280 | 325 |
281 impl<'a> TryFrom<&'a RawMessage> for Message<'a> { | |
282 type Error = ConversionError; | |
283 | |
284 /// Retrieves the data stored in this message. | |
285 fn try_from(input: &RawMessage) -> StdResult<Message, ConversionError> { | |
286 let style: Style = input.style.try_into()?; | |
287 // SAFETY: We either allocated this message ourselves or were provided it by PAM. | |
288 let result = unsafe { | |
289 match style { | |
290 Style::PromptEchoOff => Message::MaskedPrompt(input.string_data()?), | |
291 Style::PromptEchoOn => Message::Prompt(input.string_data()?), | |
292 Style::TextInfo => Message::InfoMsg(input.string_data()?), | |
293 Style::ErrorMsg => Message::ErrorMsg(input.string_data()?), | |
294 Style::RadioType => Message::ErrorMsg(input.string_data()?), | |
295 Style::BinaryPrompt => input.data.cast::<CBinaryData>().as_ref().map_or_else( | |
296 || Message::BinaryPrompt { | |
297 data_type: 0, | |
298 data: &[], | |
299 }, | |
300 |data| Message::BinaryPrompt { | |
301 data_type: data.data_type(), | |
302 data: data.contents(), | |
303 }, | |
304 ), | |
305 } | |
306 }; | |
307 Ok(result) | |
308 } | |
309 } | |
310 | |
311 #[cfg(test)] | 326 #[cfg(test)] |
312 mod tests { | 327 mod tests { |
313 use crate::conv::Message; | 328 use super::{MaskedQAndA, Questions}; |
314 use crate::libpam::message::OwnedMessages; | 329 use crate::conv::{BinaryQAndA, QAndA}; |
315 | 330 |
316 #[test] | 331 #[test] |
317 fn test_owned_messages() { | 332 fn test_owned_messages() { |
318 let mut tons_of_messages = OwnedMessages::alloc(10); | 333 let mut tons_of_messages = Questions::alloc(10); |
319 let mut msgs: Vec<_> = tons_of_messages.iter_mut().collect(); | 334 let mut msgs: Vec<_> = tons_of_messages.iter_mut().collect(); |
320 assert!(msgs.get(10).is_none()); | 335 assert!(msgs.get(10).is_none()); |
321 let last_msg = &mut msgs[9]; | 336 let last_msg = &mut msgs[9]; |
322 last_msg.set(Message::MaskedPrompt("hocus pocus")).unwrap(); | 337 last_msg |
338 .fill(&MaskedQAndA::new("hocus pocus").message()) | |
339 .unwrap(); | |
323 let another_msg = &mut msgs[0]; | 340 let another_msg = &mut msgs[0]; |
324 another_msg | 341 another_msg |
325 .set(Message::BinaryPrompt { | 342 .fill(&BinaryQAndA::new(&[5, 4, 3, 2, 1], 66).message()) |
326 data: &[5, 4, 3, 2, 1], | |
327 data_type: 99, | |
328 }) | |
329 .unwrap(); | 343 .unwrap(); |
330 let overwrite = &mut msgs[3]; | 344 let overwrite = &mut msgs[3]; |
331 overwrite.set(Message::Prompt("what")).unwrap(); | 345 overwrite.fill(&QAndA::new("what").message()).unwrap(); |
332 overwrite.set(Message::Prompt("who")).unwrap(); | 346 overwrite.fill(&QAndA::new("who").message()).unwrap(); |
333 } | 347 } |
334 } | 348 } |