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 }