comparison src/libpam/conversation.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 002adfb98c5c
comparison
equal deleted inserted replaced
76:e58d24849e82 77:351bdc13005e
1 use crate::constants::Result; 1 use crate::conv::{
2 use crate::conv::{Conversation, Message, Response}; 2 BinaryQAndA, Conversation, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA,
3 RadioQAndA,
4 };
3 use crate::libpam::memory::Immovable; 5 use crate::libpam::memory::Immovable;
4 use crate::libpam::message::{MessageIndirector, OwnedMessages}; 6 use crate::libpam::message::{Indirect, Questions};
5 use crate::libpam::response::{OwnedResponses, RawBinaryResponse, RawResponse, RawTextResponse}; 7 use crate::libpam::response::{Answer, Answers, BinaryAnswer, TextAnswer};
6 use crate::ErrorCode; 8 use crate::ErrorCode;
7 use crate::ErrorCode::ConversationError; 9 use crate::Result;
8 use std::ffi::c_int; 10 use std::ffi::c_int;
9 use std::iter; 11 use std::iter;
10 use std::marker::PhantomData; 12 use std::marker::PhantomData;
11 use std::result::Result as StdResult;
12 13
13 /// An opaque structure that is passed through PAM in a conversation. 14 /// An opaque structure that is passed through PAM in a conversation.
14 #[repr(C)] 15 #[repr(C)]
15 pub struct AppData { 16 pub struct AppData {
16 _data: (), 17 _data: (),
21 /// 22 ///
22 /// - `num_msg` is the number of messages in the `pam_message` array. 23 /// - `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 /// - `messages` is a pointer to the messages being sent to the user.
24 /// For details about its structure, see the documentation of 25 /// For details about its structure, see the documentation of
25 /// [`OwnedMessages`](super::OwnedMessages). 26 /// [`OwnedMessages`](super::OwnedMessages).
26 /// - `responses` is a pointer to an array of [`RawResponse`]s, 27 /// - `responses` is a pointer to an array of [`Answer`]s,
27 /// which PAM sets in response to a module's request. 28 /// 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 /// 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 /// There should always be exactly as many `responses` as `num_msg`.
30 /// - `appdata` is the `appdata` field of the [`LibPamConversation`] we were passed. 31 /// - `appdata` is the `appdata` field of the [`LibPamConversation`] we were passed.
31 pub type ConversationCallback = unsafe extern "C" fn( 32 pub type ConversationCallback = unsafe extern "C" fn(
32 num_msg: c_int, 33 num_msg: c_int,
33 messages: *const MessageIndirector, 34 messages: *const Indirect,
34 responses: *mut *mut RawResponse, 35 responses: *mut *mut Answer,
35 appdata: *mut AppData, 36 appdata: *mut AppData,
36 ) -> c_int; 37 ) -> c_int;
37 38
38 /// The type used by PAM to call back into a conversation. 39 /// The type used by PAM to call back into a conversation.
39 #[repr(C)] 40 #[repr(C)]
57 } 58 }
58 } 59 }
59 60
60 unsafe extern "C" fn wrapper_callback<C: Conversation>( 61 unsafe extern "C" fn wrapper_callback<C: Conversation>(
61 count: c_int, 62 count: c_int,
62 messages: *const MessageIndirector, 63 questions: *const Indirect,
63 responses: *mut *mut RawResponse, 64 answers: *mut *mut Answer,
64 me: *mut AppData, 65 me: *mut AppData,
65 ) -> c_int { 66 ) -> c_int {
66 let call = || { 67 let internal = || {
68 // Collect all our pointers
67 let conv = me 69 let conv = me
68 .cast::<C>() 70 .cast::<C>()
69 .as_mut() 71 .as_mut()
70 .ok_or(ErrorCode::ConversationError)?; 72 .ok_or(ErrorCode::ConversationError)?;
71 let indir = messages.as_ref().ok_or(ErrorCode::ConversationError)?; 73 let indirect = questions.as_ref().ok_or(ErrorCode::ConversationError)?;
72 let response_ptr = responses.as_mut().ok_or(ErrorCode::ConversationError)?; 74 let answers_ptr = answers.as_mut().ok_or(ErrorCode::ConversationError)?;
73 let messages: Vec<Message> = indir 75
76 // Build our owned list of Q&As from the questions we've been asked
77 let messages: Vec<OwnedMessage> = indirect
74 .iter(count as usize) 78 .iter(count as usize)
75 .map(Message::try_from) 79 .map(OwnedMessage::try_from)
76 .collect::<StdResult<_, _>>() 80 .collect::<Result<_>>()
77 .map_err(|_| ErrorCode::ConversationError)?; 81 .map_err(|_| ErrorCode::ConversationError)?;
78 let responses = conv.communicate(&messages)?; 82 // Borrow all those Q&As and ask them
79 let owned = 83 let borrowed: Vec<Message> = messages.iter().map(Into::into).collect();
80 OwnedResponses::build(&responses).map_err(|_| ErrorCode::ConversationError)?; 84 conv.communicate(&borrowed);
81 *response_ptr = owned.into_ptr(); 85
86 // Send our answers back
87 let owned = Answers::build(messages).map_err(|_| ErrorCode::ConversationError)?;
88 *answers_ptr = owned.into_ptr();
82 Ok(()) 89 Ok(())
83 }; 90 };
84 ErrorCode::result_to_c(call()) 91 ErrorCode::result_to_c(internal())
85 } 92 }
86 } 93 }
87 94
88 impl Conversation for LibPamConversation<'_> { 95 impl Conversation for LibPamConversation<'_> {
89 fn communicate(&mut self, messages: &[Message]) -> Result<Vec<Response>> { 96 fn communicate(&mut self, messages: &[Message]) {
90 let mut msgs_to_send = OwnedMessages::alloc(messages.len()); 97 let internal = || {
91 for (dst, src) in iter::zip(msgs_to_send.iter_mut(), messages.iter()) { 98 let mut msgs_to_send = Questions::alloc(messages.len());
92 dst.set(*src).map_err(|_| ErrorCode::ConversationError)? 99 for (dst, src) in iter::zip(msgs_to_send.iter_mut(), messages.iter()) {
100 dst.fill(src).map_err(|_| ErrorCode::ConversationError)?
101 }
102 let mut response_pointer = std::ptr::null_mut();
103 // SAFETY: We're calling into PAM with valid everything.
104 let result = unsafe {
105 (self.callback)(
106 messages.len() as c_int,
107 msgs_to_send.indirect(),
108 &mut response_pointer,
109 self.appdata,
110 )
111 };
112 ErrorCode::result_from(result)?;
113 // SAFETY: This is a pointer we just got back from PAM.
114 // We have to trust that the responses from PAM match up
115 // with the questions we sent.
116 unsafe {
117 let mut owned_responses = Answers::from_c_heap(response_pointer, messages.len());
118 for (msg, response) in iter::zip(messages, owned_responses.iter_mut()) {
119 convert(msg, response);
120 }
121 };
122 Ok(())
123 };
124 if let Err(e) = internal() {
125 messages.iter().for_each(|m| m.set_error(e))
93 } 126 }
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 } 127 }
110 } 128 }
111 129
112 fn convert_responses( 130 /// Like [`Message`], but this time we own the contents.
113 messages: &[Message], 131 pub enum OwnedMessage<'a> {
114 mut raw_responses: OwnedResponses, 132 MaskedPrompt(MaskedQAndA<'a>),
115 ) -> Result<Vec<Response>> { 133 Prompt(QAndA<'a>),
116 let pairs = iter::zip(messages.iter(), raw_responses.iter_mut()); 134 RadioPrompt(RadioQAndA<'a>),
117 // We first collect into a Vec of Results so that we always process 135 BinaryPrompt(BinaryQAndA<'a>),
118 // every single entry, which may involve freeing it. 136 Info(InfoMsg<'a>),
119 let responses: Vec<_> = pairs.map(convert).collect(); 137 Error(ErrorMsg<'a>),
120 // Only then do we return the first error, if present.
121 responses.into_iter().collect()
122 } 138 }
123 139
124 /// Converts one message-to-raw pair to a Response. 140 impl<'a> From<&'a OwnedMessage<'a>> for Message<'a> {
125 fn convert((sent, received): (&Message, &mut RawResponse)) -> Result<Response> { 141 fn from(src: &'a OwnedMessage) -> Self {
126 Ok(match sent { 142 match src {
127 Message::MaskedPrompt(_) => { 143 OwnedMessage::MaskedPrompt(m) => Message::MaskedPrompt(m),
128 // SAFETY: Since this is a response to a text message, 144 OwnedMessage::Prompt(m) => Message::Prompt(m),
129 // we know it is text. 145 OwnedMessage::RadioPrompt(m) => Message::RadioPrompt(m),
130 let text_resp = unsafe { RawTextResponse::upcast(received) }; 146 OwnedMessage::BinaryPrompt(m) => Message::BinaryPrompt(m),
131 let ret = Response::MaskedText( 147 OwnedMessage::Info(m) => Message::Info(m),
132 text_resp 148 OwnedMessage::Error(m) => Message::Error(m),
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 } 149 }
142 Message::Prompt(_) | Message::RadioPrompt(_) => { 150 }
143 // SAFETY: Since this is a response to a text message, 151 }
144 // we know it is text. 152
145 let text_resp = unsafe { RawTextResponse::upcast(received) }; 153 /// Fills in the answer of the Message with the given response.
146 let ret = Response::Text(text_resp.contents().map_err(|_| ConversationError)?.into()); 154 ///
147 // SAFETY: We're the only ones using this, 155 /// # Safety
148 // and we haven't freed it. 156 ///
149 text_resp.free_contents(); 157 /// You are responsible for ensuring that the src-dst pair matches.
150 ret 158 unsafe fn convert(msg: &Message, resp: &mut Answer) {
159 macro_rules! fill_text {
160 ($dst:ident, $src:ident) => {{let text_resp = unsafe {TextAnswer::upcast($src)};
161 $dst.set_answer(text_resp.contents().map(Into::into));}}
162 }
163 match *msg {
164 Message::MaskedPrompt(qa) => fill_text!(qa, resp),
165 Message::Prompt(qa) => fill_text!(qa, resp),
166 Message::RadioPrompt(qa) => fill_text!(qa, resp),
167 Message::Error(m) => m.set_answer(Ok(())),
168 Message::Info(m) => m.set_answer(Ok(())),
169 Message::BinaryPrompt(qa) => {
170 let bin_resp = unsafe { BinaryAnswer::upcast(resp) };
171 qa.set_answer(Ok(bin_resp.data().into()));
172 bin_resp.zero_contents()
151 } 173 }
152 Message::ErrorMsg(_) | Message::InfoMsg(_) => Response::NoResponse, 174 }
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 } 175 }