comparison src/libpam/conversation.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/conversation.rs@c7c596e6388f
children 351bdc13005e
comparison
equal deleted inserted replaced
74:c7c596e6388f 75:c30811b4afae
1 use crate::constants::Result;
2 use crate::conv::{Conversation, Message, Response};
3 use crate::libpam::memory::Immovable;
4 use crate::libpam::message::{MessageIndirector, OwnedMessages};
5 use crate::libpam::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.communicate(&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 communicate(&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::ErrorMsg(_) | Message::InfoMsg(_) => 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 }