Mercurial > crates > nonstick
comparison src/libpam/conversation.rs @ 130:80c07e5ab22f
Transfer over (almost) completely to using libpam-sys.
This reimplements everything in nonstick on top of the new -sys crate.
We don't yet use libpam-sys's helpers for binary message payloads. Soon.
| author | Paul Fisher <paul@pfish.zone> |
|---|---|
| date | Tue, 01 Jul 2025 06:11:43 -0400 |
| parents | 94b51fa4f797 |
| children | efbc235f01d3 |
comparison
equal
deleted
inserted
replaced
| 129:5b2de52dd8b2 | 130:80c07e5ab22f |
|---|---|
| 1 use crate::conv::{BinaryQAndA, RadioQAndA}; | 1 use crate::conv::{BinaryQAndA, RadioQAndA}; |
| 2 use crate::conv::{Conversation, ErrorMsg, InfoMsg, MaskedQAndA, Message, QAndA}; | 2 use crate::conv::{Conversation, ErrorMsg, Exchange, InfoMsg, MaskedQAndA, QAndA}; |
| 3 use crate::libpam::answer::BinaryAnswer; | 3 use crate::libpam::answer::BinaryAnswer; |
| 4 use crate::libpam::answer::{Answer, Answers, TextAnswer}; | 4 use crate::libpam::answer::{Answer, Answers, TextAnswer}; |
| 5 use crate::libpam::memory::CBinaryData; | 5 use crate::libpam::memory::CBinaryData; |
| 6 use crate::libpam::pam_ffi::AppData; | 6 use crate::libpam::question::Question; |
| 7 pub use crate::libpam::pam_ffi::LibPamConversation; | |
| 8 use crate::libpam::question::QuestionsTrait; | |
| 9 use crate::libpam::question::{Question, Questions}; | |
| 10 use crate::ErrorCode; | 7 use crate::ErrorCode; |
| 11 use crate::Result; | 8 use crate::Result; |
| 9 use libpam_sys::helpers::PtrPtrVec; | |
| 10 use libpam_sys::AppData; | |
| 12 use std::ffi::c_int; | 11 use std::ffi::c_int; |
| 13 use std::iter; | 12 use std::iter; |
| 14 use std::marker::PhantomData; | 13 use std::marker::PhantomData; |
| 15 use std::ptr::NonNull; | 14 use std::ptr::NonNull; |
| 16 use std::result::Result as StdResult; | 15 use std::result::Result as StdResult; |
| 17 | 16 |
| 17 /// The type used by PAM to call back into a conversation. | |
| 18 #[repr(C)] | |
| 19 pub struct LibPamConversation<'a> { | |
| 20 pam_conv: libpam_sys::pam_conv, | |
| 21 /// Marker to associate the lifetime of this with the conversation | |
| 22 /// that was passed in. | |
| 23 pub life: PhantomData<&'a mut ()>, | |
| 24 } | |
| 25 | |
| 18 impl LibPamConversation<'_> { | 26 impl LibPamConversation<'_> { |
| 19 pub fn wrap<C: Conversation>(conv: &C) -> Self { | 27 pub fn wrap<C: Conversation>(conv: &C) -> Self { |
| 20 Self { | 28 Self { |
| 21 callback: Self::wrapper_callback::<C>, | 29 pam_conv: libpam_sys::pam_conv { |
| 22 appdata: (conv as *const C).cast(), | 30 conv: Self::wrapper_callback::<C>, |
| 31 appdata_ptr: (conv as *const C).cast_mut().cast(), | |
| 32 }, | |
| 23 life: PhantomData, | 33 life: PhantomData, |
| 24 } | 34 } |
| 25 } | 35 } |
| 26 | 36 |
| 27 /// Passed as the conversation function into PAM for an owned handle. | 37 /// Passed as the conversation function into PAM for an owned handle. |
| 28 /// | 38 /// |
| 29 /// PAM calls this, we compute answers, then send them back. | 39 /// PAM calls this, we compute answers, then send them back. |
| 30 unsafe extern "C" fn wrapper_callback<C: Conversation>( | 40 unsafe extern "C" fn wrapper_callback<C: Conversation>( |
| 31 count: c_int, | 41 count: c_int, |
| 32 questions: *const *const Question, | 42 questions: *const *const libpam_sys::pam_message, |
| 33 answers: *mut *mut Answer, | 43 answers: *mut *mut libpam_sys::pam_response, |
| 34 me: *const AppData, | 44 me: *mut AppData, |
| 35 ) -> c_int { | 45 ) -> c_int { |
| 36 let internal = || { | 46 let internal = || { |
| 37 // Collect all our pointers | 47 // Collect all our pointers |
| 38 let conv = me | 48 let conv = me |
| 39 .cast::<C>() | 49 .cast::<C>() |
| 40 .as_ref() | 50 .as_ref() |
| 41 .ok_or(ErrorCode::ConversationError)?; | 51 .ok_or(ErrorCode::ConversationError)?; |
| 42 let indirect = Questions::borrow_ptr(questions, count as usize); | 52 let q_iter = PtrPtrVec::<Question>::iter_over(questions, count as usize); |
| 43 let answers_ptr = answers.as_mut().ok_or(ErrorCode::ConversationError)?; | 53 let answers_ptr = answers.as_mut().ok_or(ErrorCode::ConversationError)?; |
| 44 | 54 |
| 45 // Build our owned list of Q&As from the questions we've been asked | 55 // Build our owned list of Q&As from the questions we've been asked |
| 46 let messages: Vec<OwnedMessage> = indirect | 56 let messages: Vec<OwnedExchange> = q_iter |
| 47 .map(TryInto::try_into) | 57 .map(TryInto::try_into) |
| 48 .collect::<Result<_>>() | 58 .collect::<Result<_>>() |
| 49 .map_err(|_| ErrorCode::ConversationError)?; | 59 .map_err(|_| ErrorCode::ConversationError)?; |
| 50 // Borrow all those Q&As and ask them. | 60 // Borrow all those Q&As and ask them. |
| 51 // If we got an invalid message type, bail before sending. | 61 // If we got an invalid message type, bail before sending. |
| 52 let borrowed: Result<Vec<_>> = messages.iter().map(Message::try_from).collect(); | 62 let borrowed: Result<Vec<_>> = messages.iter().map(Exchange::try_from).collect(); |
| 53 // TODO: Do we want to log something here? | 63 // TODO: Do we want to log something here? |
| 54 conv.communicate(&borrowed?); | 64 conv.communicate(&borrowed?); |
| 55 | 65 |
| 56 // Send our answers back. | 66 // Send our answers back. |
| 57 let owned = Answers::build(messages)?; | 67 let owned = Answers::build(messages)?; |
| 58 *answers_ptr = owned.into_ptr().as_ptr(); | 68 *answers_ptr = owned.into_ptr(); |
| 59 Ok(()) | 69 Ok(()) |
| 60 }; | 70 }; |
| 61 ErrorCode::result_to_c(internal()) | 71 ErrorCode::result_to_c(internal()) |
| 62 } | 72 } |
| 63 } | 73 } |
| 64 | 74 |
| 65 impl Conversation for LibPamConversation<'_> { | 75 impl Conversation for LibPamConversation<'_> { |
| 66 fn communicate(&self, messages: &[Message]) { | 76 fn communicate(&self, messages: &[Exchange]) { |
| 67 let internal = || { | 77 let internal = || { |
| 68 let questions = Box::pin(Questions::new(messages)?); | 78 let questions: Result<_> = messages.iter().map(Question::try_from).collect(); |
| 79 let questions = PtrPtrVec::new(questions?); | |
| 69 let mut response_pointer = std::ptr::null_mut(); | 80 let mut response_pointer = std::ptr::null_mut(); |
| 70 // SAFETY: We're calling into PAM with valid everything. | 81 // SAFETY: We're calling into PAM with valid everything. |
| 71 let result = unsafe { | 82 let result = unsafe { |
| 72 (self.callback)( | 83 (self.pam_conv.conv)( |
| 73 messages.len() as c_int, | 84 messages.len() as c_int, |
| 74 questions.as_ref().ptr(), | 85 questions.as_ptr(), |
| 75 &mut response_pointer, | 86 &mut response_pointer, |
| 76 self.appdata, | 87 self.pam_conv.appdata_ptr, |
| 77 ) | 88 ) |
| 78 }; | 89 }; |
| 79 ErrorCode::result_from(result)?; | 90 ErrorCode::result_from(result)?; |
| 80 // SAFETY: This is a pointer we just got back from PAM. | 91 // SAFETY: This is a pointer we just got back from PAM. |
| 81 // We have to trust that the responses from PAM match up | 92 // We have to trust that the responses from PAM match up |
| 94 messages.iter().for_each(|m| m.set_error(e)) | 105 messages.iter().for_each(|m| m.set_error(e)) |
| 95 } | 106 } |
| 96 } | 107 } |
| 97 } | 108 } |
| 98 | 109 |
| 99 /// Like [`Message`], but this time we own the contents. | 110 /// Like [`Exchange`], but this time we own the contents. |
| 100 #[derive(Debug)] | 111 #[derive(Debug)] |
| 101 pub enum OwnedMessage<'a> { | 112 pub enum OwnedExchange<'a> { |
| 102 MaskedPrompt(MaskedQAndA<'a>), | 113 MaskedPrompt(MaskedQAndA<'a>), |
| 103 Prompt(QAndA<'a>), | 114 Prompt(QAndA<'a>), |
| 104 Info(InfoMsg<'a>), | 115 Info(InfoMsg<'a>), |
| 105 Error(ErrorMsg<'a>), | 116 Error(ErrorMsg<'a>), |
| 106 RadioPrompt(RadioQAndA<'a>), | 117 RadioPrompt(RadioQAndA<'a>), |
| 107 BinaryPrompt(BinaryQAndA<'a>), | 118 BinaryPrompt(BinaryQAndA<'a>), |
| 108 } | 119 } |
| 109 | 120 |
| 110 impl<'a> TryFrom<&'a OwnedMessage<'a>> for Message<'a> { | 121 impl<'a> TryFrom<&'a OwnedExchange<'a>> for Exchange<'a> { |
| 111 type Error = ErrorCode; | 122 type Error = ErrorCode; |
| 112 fn try_from(src: &'a OwnedMessage) -> StdResult<Self, ErrorCode> { | 123 fn try_from(src: &'a OwnedExchange) -> StdResult<Self, ErrorCode> { |
| 113 match src { | 124 match src { |
| 114 OwnedMessage::MaskedPrompt(m) => Ok(Message::MaskedPrompt(m)), | 125 OwnedExchange::MaskedPrompt(m) => Ok(Exchange::MaskedPrompt(m)), |
| 115 OwnedMessage::Prompt(m) => Ok(Message::Prompt(m)), | 126 OwnedExchange::Prompt(m) => Ok(Exchange::Prompt(m)), |
| 116 OwnedMessage::Info(m) => Ok(Message::Info(m)), | 127 OwnedExchange::Info(m) => Ok(Exchange::Info(m)), |
| 117 OwnedMessage::Error(m) => Ok(Message::Error(m)), | 128 OwnedExchange::Error(m) => Ok(Exchange::Error(m)), |
| 118 OwnedMessage::RadioPrompt(m) => Ok(Message::RadioPrompt(m)), | 129 OwnedExchange::RadioPrompt(m) => Ok(Exchange::RadioPrompt(m)), |
| 119 OwnedMessage::BinaryPrompt(m) => Ok(Message::BinaryPrompt(m)), | 130 OwnedExchange::BinaryPrompt(m) => Ok(Exchange::BinaryPrompt(m)), |
| 120 } | 131 } |
| 121 } | 132 } |
| 122 } | 133 } |
| 123 | 134 |
| 124 /// Fills in the answer of the Message with the given response. | 135 /// Fills in the answer of the Message with the given response. |
| 125 /// | 136 /// |
| 126 /// # Safety | 137 /// # Safety |
| 127 /// | 138 /// |
| 128 /// You are responsible for ensuring that the src-dst pair matches. | 139 /// You are responsible for ensuring that the src-dst pair matches. |
| 129 unsafe fn convert(msg: &Message, resp: &mut Answer) { | 140 unsafe fn convert(msg: &Exchange, resp: &mut Answer) { |
| 130 macro_rules! fill_text { | 141 macro_rules! fill_text { |
| 131 ($dst:ident, $src:ident) => {{ | 142 ($dst:ident, $src:ident) => {{ |
| 132 let text_resp = unsafe { TextAnswer::upcast($src) }; | 143 let text_resp = unsafe { TextAnswer::upcast($src) }; |
| 133 $dst.set_answer(text_resp.contents().map(Into::into)); | 144 $dst.set_answer(text_resp.contents().map(Into::into)); |
| 134 }}; | 145 }}; |
| 135 } | 146 } |
| 136 match *msg { | 147 match *msg { |
| 137 Message::MaskedPrompt(qa) => fill_text!(qa, resp), | 148 Exchange::MaskedPrompt(qa) => fill_text!(qa, resp), |
| 138 Message::Prompt(qa) => fill_text!(qa, resp), | 149 Exchange::Prompt(qa) => fill_text!(qa, resp), |
| 139 Message::Error(m) => m.set_answer(Ok(())), | 150 Exchange::Error(m) => m.set_answer(Ok(())), |
| 140 Message::Info(m) => m.set_answer(Ok(())), | 151 Exchange::Info(m) => m.set_answer(Ok(())), |
| 141 Message::RadioPrompt(qa) => fill_text!(qa, resp), | 152 Exchange::RadioPrompt(qa) => fill_text!(qa, resp), |
| 142 Message::BinaryPrompt(qa) => { | 153 Exchange::BinaryPrompt(qa) => { |
| 143 let bin_resp = unsafe { BinaryAnswer::upcast(resp) }; | 154 let bin_resp = unsafe { BinaryAnswer::upcast(resp) }; |
| 144 qa.set_answer(Ok(bin_resp | 155 qa.set_answer(Ok(bin_resp |
| 145 .data() | 156 .data() |
| 146 .map(|d| unsafe { CBinaryData::as_binary_data(d) }) | 157 .map(|d| unsafe { CBinaryData::as_binary_data(d) }) |
| 147 .unwrap_or_default())); | 158 .unwrap_or_default())); |
