comparison src/pam_ffi/message.rs @ 73:ac6881304c78

Do conversations, along with way too much stuff. This implements conversations, along with all the memory management brouhaha that goes along with it. The conversation now lives directly on the handle rather than being a thing you have to get from it and then call manually. It Turns Out this makes things a lot easier! I guess we reorganized things again. For the last time. For real. I promise. This all passes ASAN, so it seems Pretty Good!
author Paul Fisher <paul@pfish.zone>
date Thu, 05 Jun 2025 03:41:38 -0400
parents 47eb242a4f88
children c7c596e6388f
comparison
equal deleted inserted replaced
72:47eb242a4f88 73:ac6881304c78
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::pam_ffi::memory; 5 use crate::pam_ffi::memory;
5 use crate::pam_ffi::memory::{CBinaryData, NulError, TooBigError}; 6 use crate::pam_ffi::memory::{CBinaryData, Immovable, NulError, TooBigError};
6 use num_derive::FromPrimitive; 7 use num_derive::FromPrimitive;
7 use num_traits::FromPrimitive; 8 use num_traits::FromPrimitive;
8 use std::ffi::{c_char, c_int, c_void, CStr}; 9 use std::ffi::{c_int, c_void, CStr};
9 use std::result::Result as StdResult; 10 use std::result::Result as StdResult;
10 use std::slice;
11 use std::str::Utf8Error; 11 use std::str::Utf8Error;
12 12 use std::{ptr, slice};
13 /// The types of message and request that can be sent to a user.
14 ///
15 /// The data within each enum value is the prompt (or other information)
16 /// that will be presented to the user.
17 #[derive(Debug)]
18 pub enum Message<'a> {
19 /// Requests information from the user; will be masked when typing.
20 ///
21 /// Response: [`MaskedText`](crate::conv::Response::MaskedText)
22 MaskedPrompt(&'a str),
23 /// Requests information from the user; will not be masked.
24 ///
25 /// Response: [`Text`](crate::conv::Response::Text)
26 Prompt(&'a str),
27 /// "Yes/No/Maybe conditionals" (a Linux-PAM extension).
28 ///
29 /// Response: [`Text`](crate::conv::Response::Text)
30 /// (Linux-PAM documentation doesn't define its contents.)
31 RadioPrompt(&'a str),
32 /// Raises an error message to the user.
33 ///
34 /// Response: [`NoResponse`](crate::conv::Response::NoResponse)
35 Error(&'a str),
36 /// Sends an informational message to the user.
37 ///
38 /// Response: [`NoResponse`](crate::conv::Response::NoResponse)
39 Info(&'a str),
40 /// Requests binary data from the client (a Linux-PAM extension).
41 ///
42 /// This is used for non-human or non-keyboard prompts (security key?).
43 /// NOT part of the X/Open PAM specification.
44 ///
45 /// Response: [`Binary`](crate::conv::Response::Binary)
46 BinaryPrompt {
47 /// Some binary data.
48 data: &'a [u8],
49 /// A "type" that you can use for signalling. Has no strict definition in PAM.
50 data_type: u8,
51 },
52 }
53
54 impl Message<'_> {
55 /// Copies the contents of this message to the C heap.
56 fn copy_to_heap(&self) -> StdResult<(Style, *mut c_void), ConversionError> {
57 let alloc = |style, text| Ok((style, memory::malloc_str(text)?.cast()));
58 match *self {
59 Self::MaskedPrompt(text) => alloc(Style::PromptEchoOff, text),
60 Self::Prompt(text) => alloc(Style::PromptEchoOn, text),
61 Self::RadioPrompt(text) => alloc(Style::RadioType, text),
62 Self::Error(text) => alloc(Style::ErrorMsg, text),
63 Self::Info(text) => alloc(Style::TextInfo, text),
64 Self::BinaryPrompt { data, data_type } => Ok((
65 Style::BinaryPrompt,
66 (CBinaryData::alloc(data, data_type)?).cast(),
67 )),
68 }
69 }
70 }
71 13
72 #[derive(Debug, thiserror::Error)] 14 #[derive(Debug, thiserror::Error)]
73 #[error("error creating PAM message: {0}")] 15 #[error("error creating PAM message: {0}")]
74 enum ConversionError { 16 pub enum ConversionError {
75 InvalidEnum(#[from] InvalidEnum<Style>), 17 InvalidEnum(#[from] InvalidEnum<Style>),
76 Utf8Error(#[from] Utf8Error), 18 Utf8Error(#[from] Utf8Error),
77 NulError(#[from] NulError), 19 NulError(#[from] NulError),
78 TooBigError(#[from] TooBigError), 20 TooBigError(#[from] TooBigError),
79 } 21 }
119 /// The style of message to request. 61 /// The style of message to request.
120 style: c_int, 62 style: c_int,
121 /// A description of the data requested. 63 /// A description of the data requested.
122 /// 64 ///
123 /// For most requests, this will be an owned [`CStr`], but for requests 65 /// For most requests, this will be an owned [`CStr`], but for requests
124 /// with [`Style::BinaryPrompt`], this will be [`BinaryData`] 66 /// with [`Style::BinaryPrompt`], this will be [`CBinaryData`]
125 /// (a Linux-PAM extension). 67 /// (a Linux-PAM extension).
126 data: *mut c_void, 68 data: *mut c_void,
69 _marker: Immovable,
127 } 70 }
128 71
129 impl RawMessage { 72 impl RawMessage {
130 fn set(&mut self, msg: &Message) -> StdResult<(), ConversionError> { 73 pub fn set(&mut self, msg: Message) -> StdResult<(), ConversionError> {
131 let (style, data) = msg.copy_to_heap()?; 74 let (style, data) = copy_to_heap(msg)?;
132 self.clear(); 75 self.clear();
133 // SAFETY: We allocated this ourselves or were given it by PAM. 76 // SAFETY: We allocated this ourselves or were given it by PAM.
134 // Otherwise, it's null, but free(null) is fine. 77 // Otherwise, it's null, but free(null) is fine.
135 unsafe { libc::free(self.data) }; 78 unsafe { libc::free(self.data) };
136 self.style = style as c_int; 79 self.style = style as c_int;
137 self.data = data; 80 self.data = data;
138 Ok(()) 81 Ok(())
139 } 82 }
140 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::Error(text) => alloc(Style::ErrorMsg, text),
130 Message::Info(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
141 /// Retrieves the data stored in this message. 284 /// Retrieves the data stored in this message.
142 fn data(&self) -> StdResult<Message, ConversionError> { 285 fn try_from(input: &RawMessage) -> StdResult<Message, ConversionError> {
143 let style: Style = self.style.try_into()?; 286 let style: Style = input.style.try_into()?;
144 // SAFETY: We either allocated this message ourselves or were provided it by PAM. 287 // SAFETY: We either allocated this message ourselves or were provided it by PAM.
145 let result = unsafe { 288 let result = unsafe {
146 match style { 289 match style {
147 Style::PromptEchoOff => Message::MaskedPrompt(self.string_data()?), 290 Style::PromptEchoOff => Message::MaskedPrompt(input.string_data()?),
148 Style::PromptEchoOn => Message::Prompt(self.string_data()?), 291 Style::PromptEchoOn => Message::Prompt(input.string_data()?),
149 Style::TextInfo => Message::Info(self.string_data()?), 292 Style::TextInfo => Message::Info(input.string_data()?),
150 Style::ErrorMsg => Message::Error(self.string_data()?), 293 Style::ErrorMsg => Message::Error(input.string_data()?),
151 Style::RadioType => Message::Error(self.string_data()?), 294 Style::RadioType => Message::Error(input.string_data()?),
152 Style::BinaryPrompt => (self.data as *const CBinaryData).as_ref().map_or_else( 295 Style::BinaryPrompt => input.data.cast::<CBinaryData>().as_ref().map_or_else(
153 || Message::BinaryPrompt { 296 || Message::BinaryPrompt {
154 data_type: 0, 297 data_type: 0,
155 data: &[], 298 data: &[],
156 }, 299 },
157 |data| Message::BinaryPrompt { 300 |data| Message::BinaryPrompt {
161 ), 304 ),
162 } 305 }
163 }; 306 };
164 Ok(result) 307 Ok(result)
165 } 308 }
166 309 }
167 /// Gets this message's data pointer as a string. 310
168 /// 311 #[cfg(test)]
169 /// # Safety 312 mod tests {
170 /// 313 use crate::conv::Message;
171 /// It's up to you to pass this only on types with a string value. 314 use crate::pam_ffi::message::OwnedMessages;
172 unsafe fn string_data(&self) -> StdResult<&str, Utf8Error> { 315
173 if self.data.is_null() { 316 #[test]
174 Ok("") 317 fn test_owned_messages() {
175 } else { 318 let mut tons_of_messages = OwnedMessages::alloc(10);
176 CStr::from_ptr(self.data as *const c_char).to_str() 319 let mut msgs: Vec<_> = tons_of_messages.iter_mut().collect();
177 } 320 assert!(msgs.get(10).is_none());
178 } 321 let last_msg = &mut msgs[9];
179 322 last_msg.set(Message::MaskedPrompt("hocus pocus")).unwrap();
180 /// Zeroes out the data stored here. 323 let another_msg = &mut msgs[0];
181 fn clear(&mut self) { 324 another_msg
182 // SAFETY: We either created this data or we got it from PAM. 325 .set(Message::BinaryPrompt {
183 // After this function is done, it will be zeroed out. 326 data: &[5, 4, 3, 2, 1],
184 unsafe { 327 data_type: 99,
185 if let Ok(style) = Style::try_from(self.style) { 328 })
186 match style { 329 .unwrap();
187 Style::BinaryPrompt => { 330 let overwrite = &mut msgs[3];
188 if let Some(d) = (self.data as *mut CBinaryData).as_mut() { 331 overwrite.set(Message::Prompt("what")).unwrap();
189 d.zero_contents() 332 overwrite.set(Message::Prompt("who")).unwrap();
190 } 333 }
191 } 334 }
192 Style::TextInfo
193 | Style::RadioType
194 | Style::ErrorMsg
195 | Style::PromptEchoOff
196 | Style::PromptEchoOn => memory::zero_c_string(self.data),
197 }
198 };
199 }
200 }
201 }
202
203 /// Abstraction of a list-of-messages to be sent in a PAM conversation.
204 ///
205 /// On Linux-PAM and other compatible implementations, `messages`
206 /// is treated as a pointer-to-pointers, like `int argc, char **argv`.
207 /// (In this situation, the value of `OwnedMessages.indirect` is
208 /// the pointer passed to `pam_conv`.)
209 ///
210 /// ```text
211 /// ╔═ OwnedMsgs ═╗ points to ┌─ Indirect ─┐ ╔═ Message ═╗
212 /// ║ indirect ┄┄┄╫┄┄┄┄┄┄┄┄┄┄┄> │ base[0] ┄┄┄┼┄┄┄┄┄> ║ style ║
213 /// ║ count ║ │ base[1] ┄┄┄┼┄┄┄╮ ║ data ║
214 /// ╚═════════════╝ │ ... │ ┆ ╚═══════════╝
215 /// ┆
216 /// ┆ ╔═ Message ═╗
217 /// ╰┄┄> ║ style ║
218 /// ║ data ║
219 /// ╚═══════════╝
220 /// ```
221 ///
222 /// On OpenPAM and other compatible implementations (like Solaris),
223 /// `messages` is a pointer-to-pointer-to-array. This appears to be
224 /// the correct implementation as required by the XSSO specification.
225 ///
226 /// ```text
227 /// ╔═ OwnedMsgs ═╗ points to ┌─ Indirect ─┐ ╔═ Message[] ═╗
228 /// ║ indirect ┄┄┄╫┄┄┄┄┄┄┄┄┄┄┄> │ base ┄┄┄┄┄┄┼┄┄┄┄┄> ║ style ║
229 /// ║ count ║ └────────────┘ ║ data ║
230 /// ╚═════════════╝ ╟─────────────╢
231 /// ║ style ║
232 /// ║ data ║
233 /// ╟─────────────╢
234 /// ║ ... ║
235 /// ```
236 ///
237 /// ***THIS LIBRARY CURRENTLY SUPPORTS ONLY LINUX-PAM.***
238 #[repr(C)]
239 pub struct OwnedMessages {
240 /// An indirection to the messages themselves, stored on the C heap.
241 indirect: *mut Indirect<RawMessage>,
242 /// The number of messages in the list.
243 count: usize,
244 }
245
246 impl OwnedMessages {
247 /// Allocates data to store messages on the C heap.
248 pub fn alloc(count: usize) -> Self {
249 // SAFETY: We're allocating. That's safe.
250 unsafe {
251 // Since this is Linux-PAM, the indirect is a list of pointers.
252 let indirect =
253 libc::calloc(count, size_of::<Indirect<RawMessage>>()) as *mut Indirect<RawMessage>;
254 let indir_ptrs = slice::from_raw_parts_mut(indirect, count);
255 for ptr in indir_ptrs {
256 ptr.base = libc::calloc(1, size_of::<RawMessage>()) as *mut RawMessage;
257 }
258 Self { indirect, count }
259 }
260 }
261
262 /// Gets a reference to the message at the given index.
263 pub fn get(&self, index: usize) -> Option<&RawMessage> {
264 (index < self.count).then(|| unsafe { (*self.indirect).at(index) })
265 }
266
267 /// Gets a mutable reference to the message at the given index.
268 pub fn get_mut(&mut self, index: usize) -> Option<&mut RawMessage> {
269 (index < self.count).then(|| unsafe { (*self.indirect).at_mut(index) })
270 }
271 }
272
273 #[repr(transparent)]
274 struct Indirect<T> {
275 /// The starting address for the T.
276 base: *mut T,
277 }
278
279 impl<T> Indirect<T> {
280 /// Gets a mutable reference to the element at the given index.
281 ///
282 /// # Safety
283 ///
284 /// We don't check `index`.
285 unsafe fn at_mut(&mut self, index: usize) -> &mut T {
286 &mut *self.base.add(index)
287 }
288
289 unsafe fn at(&self, index: usize) -> &T {
290 &*self.base.add(index)
291 }
292 }