Mercurial > crates > nonstick
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 } |
