Mercurial > crates > nonstick
comparison src/libpam/message.rs @ 75:c30811b4afae
rename pam_ffi submodule to libpam.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Fri, 06 Jun 2025 22:35:08 -0400 |
parents | src/pam_ffi/message.rs@c7c596e6388f |
children | 351bdc13005e |
comparison
equal
deleted
inserted
replaced
74:c7c596e6388f | 75:c30811b4afae |
---|---|
1 //! Data and types dealing with PAM messages. | |
2 | |
3 use crate::constants::InvalidEnum; | |
4 use crate::conv::Message; | |
5 use crate::libpam::memory; | |
6 use crate::libpam::memory::{CBinaryData, Immovable, NulError, TooBigError}; | |
7 use num_derive::FromPrimitive; | |
8 use num_traits::FromPrimitive; | |
9 use std::ffi::{c_int, c_void, CStr}; | |
10 use std::result::Result as StdResult; | |
11 use std::str::Utf8Error; | |
12 use std::{ptr, slice}; | |
13 | |
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 | |
23 /// The C enum values for messages shown to the user. | |
24 #[derive(Debug, PartialEq, FromPrimitive)] | |
25 pub enum Style { | |
26 /// Requests information from the user; will be masked when typing. | |
27 PromptEchoOff = 1, | |
28 /// Requests information from the user; will not be masked. | |
29 PromptEchoOn = 2, | |
30 /// An error message. | |
31 ErrorMsg = 3, | |
32 /// An informational message. | |
33 TextInfo = 4, | |
34 /// Yes/No/Maybe conditionals. A Linux-PAM extension. | |
35 RadioType = 5, | |
36 /// For server–client non-human interaction. | |
37 /// | |
38 /// NOT part of the X/Open PAM specification. | |
39 /// A Linux-PAM extension. | |
40 BinaryPrompt = 7, | |
41 } | |
42 | |
43 impl TryFrom<c_int> for Style { | |
44 type Error = InvalidEnum<Self>; | |
45 fn try_from(value: c_int) -> StdResult<Self, Self::Error> { | |
46 Self::from_i32(value).ok_or(value.into()) | |
47 } | |
48 } | |
49 | |
50 impl From<Style> for c_int { | |
51 fn from(val: Style) -> Self { | |
52 val as Self | |
53 } | |
54 } | |
55 | |
56 /// A message sent by PAM or a module to an application. | |
57 /// This message, and its internal data, is owned by the creator | |
58 /// (either the module or PAM itself). | |
59 #[repr(C)] | |
60 pub struct RawMessage { | |
61 /// The style of message to request. | |
62 style: c_int, | |
63 /// A description of the data requested. | |
64 /// | |
65 /// For most requests, this will be an owned [`CStr`], but for requests | |
66 /// with [`Style::BinaryPrompt`], this will be [`CBinaryData`] | |
67 /// (a Linux-PAM extension). | |
68 data: *mut c_void, | |
69 _marker: Immovable, | |
70 } | |
71 | |
72 impl RawMessage { | |
73 pub fn set(&mut self, msg: Message) -> StdResult<(), ConversionError> { | |
74 let (style, data) = copy_to_heap(msg)?; | |
75 self.clear(); | |
76 // SAFETY: We allocated this ourselves or were given it by PAM. | |
77 // Otherwise, it's null, but free(null) is fine. | |
78 unsafe { libc::free(self.data) }; | |
79 self.style = style as c_int; | |
80 self.data = data; | |
81 Ok(()) | |
82 } | |
83 | |
84 /// Gets this message's data pointer as a string. | |
85 /// | |
86 /// # Safety | |
87 /// | |
88 /// It's up to you to pass this only on types with a string value. | |
89 unsafe fn string_data(&self) -> StdResult<&str, Utf8Error> { | |
90 if self.data.is_null() { | |
91 Ok("") | |
92 } else { | |
93 CStr::from_ptr(self.data.cast()).to_str() | |
94 } | |
95 } | |
96 | |
97 /// Zeroes out the data stored here. | |
98 fn clear(&mut self) { | |
99 // SAFETY: We either created this data or we got it from PAM. | |
100 // After this function is done, it will be zeroed out. | |
101 unsafe { | |
102 if let Ok(style) = Style::try_from(self.style) { | |
103 match style { | |
104 Style::BinaryPrompt => { | |
105 if let Some(d) = self.data.cast::<CBinaryData>().as_mut() { | |
106 d.zero_contents() | |
107 } | |
108 } | |
109 Style::TextInfo | |
110 | Style::RadioType | |
111 | Style::ErrorMsg | |
112 | Style::PromptEchoOff | |
113 | Style::PromptEchoOn => memory::zero_c_string(self.data), | |
114 } | |
115 }; | |
116 libc::free(self.data); | |
117 self.data = ptr::null_mut(); | |
118 } | |
119 } | |
120 } | |
121 | |
122 /// Copies the contents of this message to the C heap. | |
123 fn copy_to_heap(msg: Message) -> StdResult<(Style, *mut c_void), ConversionError> { | |
124 let alloc = |style, text| Ok((style, memory::malloc_str(text)?.cast())); | |
125 match msg { | |
126 Message::MaskedPrompt(text) => alloc(Style::PromptEchoOff, text), | |
127 Message::Prompt(text) => alloc(Style::PromptEchoOn, text), | |
128 Message::RadioPrompt(text) => alloc(Style::RadioType, text), | |
129 Message::ErrorMsg(text) => alloc(Style::ErrorMsg, text), | |
130 Message::InfoMsg(text) => alloc(Style::TextInfo, text), | |
131 Message::BinaryPrompt { data, data_type } => Ok(( | |
132 Style::BinaryPrompt, | |
133 (CBinaryData::alloc(data, data_type)?).cast(), | |
134 )), | |
135 } | |
136 } | |
137 | |
138 /// Abstraction of a list-of-messages to be sent in a PAM conversation. | |
139 /// | |
140 /// On Linux-PAM and other compatible implementations, `messages` | |
141 /// is treated as a pointer-to-pointers, like `int argc, char **argv`. | |
142 /// (In this situation, the value of `OwnedMessages.indirect` is | |
143 /// the pointer passed to `pam_conv`.) | |
144 /// | |
145 /// ```text | |
146 /// ╔═ OwnedMsgs ═╗ points to ┌─ Indirect ─┐ ╔═ Message ═╗ | |
147 /// ║ indirect ┄┄┄╫┄┄┄┄┄┄┄┄┄┄┄> │ base[0] ┄┄┄┼┄┄┄┄┄> ║ style ║ | |
148 /// ║ count ║ │ base[1] ┄┄┄┼┄┄┄╮ ║ data ║ | |
149 /// ╚═════════════╝ │ ... │ ┆ ╚═══════════╝ | |
150 /// ┆ | |
151 /// ┆ ╔═ Message ═╗ | |
152 /// ╰┄┄> ║ style ║ | |
153 /// ║ data ║ | |
154 /// ╚═══════════╝ | |
155 /// ``` | |
156 /// | |
157 /// On OpenPAM and other compatible implementations (like Solaris), | |
158 /// `messages` is a pointer-to-pointer-to-array. This appears to be | |
159 /// the correct implementation as required by the XSSO specification. | |
160 /// | |
161 /// ```text | |
162 /// ╔═ OwnedMsgs ═╗ points to ┌─ Indirect ─┐ ╔═ Message[] ═╗ | |
163 /// ║ indirect ┄┄┄╫┄┄┄┄┄┄┄┄┄┄┄> │ base ┄┄┄┄┄┄┼┄┄┄┄┄> ║ style ║ | |
164 /// ║ count ║ └────────────┘ ║ data ║ | |
165 /// ╚═════════════╝ ╟─────────────╢ | |
166 /// ║ style ║ | |
167 /// ║ data ║ | |
168 /// ╟─────────────╢ | |
169 /// ║ ... ║ | |
170 /// ``` | |
171 /// | |
172 /// ***THIS LIBRARY CURRENTLY SUPPORTS ONLY LINUX-PAM.*** | |
173 pub struct OwnedMessages { | |
174 /// An indirection to the messages themselves, stored on the C heap. | |
175 indirect: *mut MessageIndirector, | |
176 /// The number of messages in the list. | |
177 count: usize, | |
178 } | |
179 | |
180 impl OwnedMessages { | |
181 /// Allocates data to store messages on the C heap. | |
182 pub fn alloc(count: usize) -> Self { | |
183 Self { | |
184 indirect: MessageIndirector::alloc(count), | |
185 count, | |
186 } | |
187 } | |
188 | |
189 /// The pointer to the thing with the actual list. | |
190 pub fn indirector(&self) -> *const MessageIndirector { | |
191 self.indirect | |
192 } | |
193 | |
194 pub fn iter(&self) -> impl Iterator<Item = &RawMessage> { | |
195 // SAFETY: we're iterating over an amount we know. | |
196 unsafe { (*self.indirect).iter(self.count) } | |
197 } | |
198 | |
199 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut RawMessage> { | |
200 // SAFETY: we're iterating over an amount we know. | |
201 unsafe { (*self.indirect).iter_mut(self.count) } | |
202 } | |
203 } | |
204 | |
205 impl Drop for OwnedMessages { | |
206 fn drop(&mut self) { | |
207 // SAFETY: We are valid and have a valid pointer. | |
208 // Once we're done, everything will be safe. | |
209 unsafe { | |
210 if let Some(indirect) = self.indirect.as_mut() { | |
211 indirect.free(self.count) | |
212 } | |
213 libc::free(self.indirect.cast()); | |
214 self.indirect = ptr::null_mut(); | |
215 } | |
216 } | |
217 } | |
218 | |
219 /// An indirect reference to messages. | |
220 /// | |
221 /// 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. | |
223 #[repr(transparent)] | |
224 pub struct MessageIndirector { | |
225 base: [*mut RawMessage; 0], | |
226 _marker: Immovable, | |
227 } | |
228 | |
229 impl MessageIndirector { | |
230 /// Allocates memory for this indirector and all its members. | |
231 fn alloc(count: usize) -> *mut Self { | |
232 // SAFETY: We're only allocating, and when we're done, | |
233 // everything will be in a known-good state. | |
234 unsafe { | |
235 let me_ptr: *mut MessageIndirector = | |
236 libc::calloc(count, size_of::<*mut RawMessage>()).cast(); | |
237 let me = &mut *me_ptr; | |
238 let ptr_list = slice::from_raw_parts_mut(me.base.as_mut_ptr(), count); | |
239 for entry in ptr_list { | |
240 *entry = libc::calloc(1, size_of::<RawMessage>()).cast(); | |
241 } | |
242 me | |
243 } | |
244 } | |
245 | |
246 /// Returns an iterator yielding the given number of messages. | |
247 /// | |
248 /// # Safety | |
249 /// | |
250 /// You have to provide the right count. | |
251 pub unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &RawMessage> { | |
252 (0..count).map(|idx| &**self.base.as_ptr().add(idx)) | |
253 } | |
254 | |
255 /// Returns a mutable iterator yielding the given number of messages. | |
256 /// | |
257 /// # Safety | |
258 /// | |
259 /// You have to provide the right count. | |
260 pub unsafe fn iter_mut(&mut self, count: usize) -> impl Iterator<Item = &mut RawMessage> { | |
261 (0..count).map(|idx| &mut **self.base.as_mut_ptr().add(idx)) | |
262 } | |
263 | |
264 /// Frees this and everything it points to. | |
265 /// | |
266 /// # Safety | |
267 /// | |
268 /// You have to pass the right size. | |
269 unsafe fn free(&mut self, count: usize) { | |
270 let msgs = slice::from_raw_parts_mut(self.base.as_mut_ptr(), count); | |
271 for msg in msgs { | |
272 if let Some(msg) = msg.as_mut() { | |
273 msg.clear(); | |
274 } | |
275 libc::free(msg.cast()); | |
276 *msg = ptr::null_mut(); | |
277 } | |
278 } | |
279 } | |
280 | |
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)] | |
312 mod tests { | |
313 use crate::conv::Message; | |
314 use crate::libpam::message::OwnedMessages; | |
315 | |
316 #[test] | |
317 fn test_owned_messages() { | |
318 let mut tons_of_messages = OwnedMessages::alloc(10); | |
319 let mut msgs: Vec<_> = tons_of_messages.iter_mut().collect(); | |
320 assert!(msgs.get(10).is_none()); | |
321 let last_msg = &mut msgs[9]; | |
322 last_msg.set(Message::MaskedPrompt("hocus pocus")).unwrap(); | |
323 let another_msg = &mut msgs[0]; | |
324 another_msg | |
325 .set(Message::BinaryPrompt { | |
326 data: &[5, 4, 3, 2, 1], | |
327 data_type: 99, | |
328 }) | |
329 .unwrap(); | |
330 let overwrite = &mut msgs[3]; | |
331 overwrite.set(Message::Prompt("what")).unwrap(); | |
332 overwrite.set(Message::Prompt("who")).unwrap(); | |
333 } | |
334 } |