Mercurial > crates > nonstick
comparison src/pam_ffi/conversation.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 | |
children | c7c596e6388f |
comparison
equal
deleted
inserted
replaced
72:47eb242a4f88 | 73:ac6881304c78 |
---|---|
1 use crate::constants::Result; | |
2 use crate::conv::{Conversation, Message, Response}; | |
3 use crate::pam_ffi::memory::Immovable; | |
4 use crate::pam_ffi::message::{MessageIndirector, OwnedMessages}; | |
5 use crate::pam_ffi::response::{OwnedResponses, RawBinaryResponse, RawResponse, RawTextResponse}; | |
6 use crate::ErrorCode; | |
7 use crate::ErrorCode::ConversationError; | |
8 use std::ffi::c_int; | |
9 use std::iter; | |
10 use std::marker::PhantomData; | |
11 use std::result::Result as StdResult; | |
12 | |
13 /// An opaque structure that is passed through PAM in a conversation. | |
14 #[repr(C)] | |
15 pub struct AppData { | |
16 _data: (), | |
17 _marker: Immovable, | |
18 } | |
19 | |
20 /// The callback that PAM uses to get information in a conversation. | |
21 /// | |
22 /// - `num_msg` is the number of messages in the `pam_message` array. | |
23 /// - `messages` is a pointer to the messages being sent to the user. | |
24 /// For details about its structure, see the documentation of | |
25 /// [`OwnedMessages`](super::OwnedMessages). | |
26 /// - `responses` is a pointer to an array of [`RawResponse`]s, | |
27 /// which PAM sets in response to a module's request. | |
28 /// This is an array of structs, not an array of pointers to a struct. | |
29 /// There should always be exactly as many `responses` as `num_msg`. | |
30 /// - `appdata` is the `appdata` field of the [`LibPamConversation`] we were passed. | |
31 pub type ConversationCallback = unsafe extern "C" fn( | |
32 num_msg: c_int, | |
33 messages: *const MessageIndirector, | |
34 responses: *mut *mut RawResponse, | |
35 appdata: *mut AppData, | |
36 ) -> c_int; | |
37 | |
38 /// The type used by PAM to call back into a conversation. | |
39 #[repr(C)] | |
40 pub struct LibPamConversation<'a> { | |
41 /// The function that is called to get information from the user. | |
42 callback: ConversationCallback, | |
43 /// The pointer that will be passed as the last parameter | |
44 /// to the conversation callback. | |
45 appdata: *mut AppData, | |
46 life: PhantomData<&'a mut ()>, | |
47 _marker: Immovable, | |
48 } | |
49 | |
50 impl LibPamConversation<'_> { | |
51 fn wrap<C: Conversation>(conv: &mut C) -> Self { | |
52 Self { | |
53 callback: Self::wrapper_callback::<C>, | |
54 appdata: (conv as *mut C).cast(), | |
55 life: PhantomData, | |
56 _marker: Immovable(PhantomData), | |
57 } | |
58 } | |
59 | |
60 unsafe extern "C" fn wrapper_callback<C: Conversation>( | |
61 count: c_int, | |
62 messages: *const MessageIndirector, | |
63 responses: *mut *mut RawResponse, | |
64 me: *mut AppData, | |
65 ) -> c_int { | |
66 let call = || { | |
67 let conv = me | |
68 .cast::<C>() | |
69 .as_mut() | |
70 .ok_or(ErrorCode::ConversationError)?; | |
71 let indir = messages.as_ref().ok_or(ErrorCode::ConversationError)?; | |
72 let response_ptr = responses.as_mut().ok_or(ErrorCode::ConversationError)?; | |
73 let messages: Vec<Message> = indir | |
74 .iter(count as usize) | |
75 .map(Message::try_from) | |
76 .collect::<StdResult<_, _>>() | |
77 .map_err(|_| ErrorCode::ConversationError)?; | |
78 let responses = conv.converse(&messages)?; | |
79 let owned = | |
80 OwnedResponses::build(&responses).map_err(|_| ErrorCode::ConversationError)?; | |
81 *response_ptr = owned.into_ptr(); | |
82 Ok(()) | |
83 }; | |
84 ErrorCode::result_to_c(call()) | |
85 } | |
86 } | |
87 | |
88 impl Conversation for LibPamConversation<'_> { | |
89 fn converse(&mut self, messages: &[Message]) -> Result<Vec<Response>> { | |
90 let mut msgs_to_send = OwnedMessages::alloc(messages.len()); | |
91 for (dst, src) in iter::zip(msgs_to_send.iter_mut(), messages.iter()) { | |
92 dst.set(*src).map_err(|_| ErrorCode::ConversationError)? | |
93 } | |
94 let mut response_pointer = std::ptr::null_mut(); | |
95 // SAFETY: We're calling into PAM with valid everything. | |
96 let result = unsafe { | |
97 (self.callback)( | |
98 messages.len() as c_int, | |
99 msgs_to_send.indirector(), | |
100 &mut response_pointer, | |
101 self.appdata, | |
102 ) | |
103 }; | |
104 ErrorCode::result_from(result)?; | |
105 // SAFETY: This is a pointer we just got back from PAM. | |
106 let owned_responses = | |
107 unsafe { OwnedResponses::from_c_heap(response_pointer, messages.len()) }; | |
108 convert_responses(messages, owned_responses) | |
109 } | |
110 } | |
111 | |
112 fn convert_responses( | |
113 messages: &[Message], | |
114 mut raw_responses: OwnedResponses, | |
115 ) -> Result<Vec<Response>> { | |
116 let pairs = iter::zip(messages.iter(), raw_responses.iter_mut()); | |
117 // We first collect into a Vec of Results so that we always process | |
118 // every single entry, which may involve freeing it. | |
119 let responses: Vec<_> = pairs.map(convert).collect(); | |
120 // Only then do we return the first error, if present. | |
121 responses.into_iter().collect() | |
122 } | |
123 | |
124 /// Converts one message-to-raw pair to a Response. | |
125 fn convert((sent, received): (&Message, &mut RawResponse)) -> Result<Response> { | |
126 Ok(match sent { | |
127 Message::MaskedPrompt(_) => { | |
128 // SAFETY: Since this is a response to a text message, | |
129 // we know it is text. | |
130 let text_resp = unsafe { RawTextResponse::upcast(received) }; | |
131 let ret = Response::MaskedText( | |
132 text_resp | |
133 .contents() | |
134 .map_err(|_| ErrorCode::ConversationError)? | |
135 .into(), | |
136 ); | |
137 // SAFETY: We're the only ones using this, | |
138 // and we haven't freed it. | |
139 text_resp.free_contents(); | |
140 ret | |
141 } | |
142 Message::Prompt(_) | Message::RadioPrompt(_) => { | |
143 // SAFETY: Since this is a response to a text message, | |
144 // we know it is text. | |
145 let text_resp = unsafe { RawTextResponse::upcast(received) }; | |
146 let ret = Response::Text(text_resp.contents().map_err(|_| ConversationError)?.into()); | |
147 // SAFETY: We're the only ones using this, | |
148 // and we haven't freed it. | |
149 text_resp.free_contents(); | |
150 ret | |
151 } | |
152 Message::Error(_) | Message::Info(_) => Response::NoResponse, | |
153 Message::BinaryPrompt { .. } => { | |
154 let bin_resp = unsafe { RawBinaryResponse::upcast(received) }; | |
155 let ret = Response::Binary(bin_resp.to_owned()); | |
156 // SAFETY: We're the only ones using this, | |
157 // and we haven't freed it. | |
158 bin_resp.free_contents(); | |
159 ret | |
160 } | |
161 }) | |
162 } |