comparison src/conv.rs @ 71:58f9d2a4df38

Reorganize everything again??? - Splits ffi/memory stuff into a bunch of stuff in the pam_ffi module. - Builds infrastructure for passing Messages and Responses. - Adds tests for some things at least.
author Paul Fisher <paul@pfish.zone>
date Tue, 03 Jun 2025 21:54:58 -0400
parents 9f8381a1c09c
children 47eb242a4f88
comparison
equal deleted inserted replaced
70:9f8381a1c09c 71:58f9d2a4df38
1 //! The PAM conversation and associated Stuff. 1 //! The PAM conversation and associated Stuff.
2 2
3 // Temporarily allowed until we get the actual conversation functions hooked up. 3 // Temporarily allowed until we get the actual conversation functions hooked up.
4 #![allow(dead_code)] 4 #![allow(dead_code)]
5 5
6 use crate::constants::{NulError, Result, TooBigError}; 6 use crate::constants::Result;
7 use crate::pam_ffi::{BinaryResponseInner, GenericResponse, TextResponseInner}; 7 use crate::pam_ffi::Message;
8 use secure_string::SecureString; 8 use secure_string::SecureString;
9 use std::mem;
10 use std::result::Result as StdResult;
11 use std::str::Utf8Error;
12
13 // TODO: In most cases, we should be passing around references to strings 9 // TODO: In most cases, we should be passing around references to strings
14 // or binary data. Right now we don't because that turns type inference and 10 // or binary data. Right now we don't because that turns type inference and
15 // trait definitions/implementations into a HUGE MESS. 11 // trait definitions/implementations into a HUGE MESS.
16 // 12 //
17 // Ideally, we would be using some kind of `TD: TextData` and `BD: BinaryData` 13 // Ideally, we would be using some kind of `TD: TextData` and `BD: BinaryData`
18 // associated types in the various Conversation traits to avoid copying 14 // associated types in the various Conversation traits to avoid copying
19 // when unnecessary. 15 // when unnecessary.
20
21 /// The types of message and request that can be sent to a user.
22 ///
23 /// The data within each enum value is the prompt (or other information)
24 /// that will be presented to the user.
25 #[derive(Debug)]
26 pub enum Message<'a> {
27 /// Requests information from the user; will be masked when typing.
28 ///
29 /// Response: [`Response::MaskedText`]
30 MaskedPrompt(&'a str),
31 /// Requests information from the user; will not be masked.
32 ///
33 /// Response: [`Response::Text`]
34 Prompt(&'a str),
35 /// "Yes/No/Maybe conditionals" (a Linux-PAM extension).
36 ///
37 /// Response: [`Response::Text`]
38 /// (Linux-PAM documentation doesn't define its contents.)
39 RadioPrompt(&'a str),
40 /// Raises an error message to the user.
41 ///
42 /// Response: [`Response::NoResponse`]
43 Error(&'a str),
44 /// Sends an informational message to the user.
45 ///
46 /// Response: [`Response::NoResponse`]
47 Info(&'a str),
48 /// Requests binary data from the client (a Linux-PAM extension).
49 ///
50 /// This is used for non-human or non-keyboard prompts (security key?).
51 /// NOT part of the X/Open PAM specification.
52 ///
53 /// Response: [`Response::Binary`]
54 BinaryPrompt {
55 /// Some binary data.
56 data: &'a [u8],
57 /// A "type" that you can use for signalling. Has no strict definition in PAM.
58 data_type: u8,
59 },
60 }
61 16
62 /// The responses that PAM will return from a request. 17 /// The responses that PAM will return from a request.
63 #[derive(Debug, PartialEq, derive_more::From)] 18 #[derive(Debug, PartialEq, derive_more::From)]
64 pub enum Response { 19 pub enum Response {
65 /// Used to fill in list entries where there is no response expected. 20 /// Used to fill in list entries where there is no response expected.
101 pub trait Conversation { 56 pub trait Conversation {
102 /// Sends messages to the user. 57 /// Sends messages to the user.
103 /// 58 ///
104 /// The returned Vec of messages always contains exactly as many entries 59 /// The returned Vec of messages always contains exactly as many entries
105 /// as there were messages in the request; one corresponding to each. 60 /// as there were messages in the request; one corresponding to each.
106 ///
107 /// Messages with no response (e.g. [info](Message::Info) and
108 /// [error](Message::Error)) will have a `None` entry instead of a `Response`.
109 fn send(&mut self, messages: &[Message]) -> Result<Vec<Response>>; 61 fn send(&mut self, messages: &[Message]) -> Result<Vec<Response>>;
110 } 62 }
111 63
112 /// Trait that an application can implement if they want to handle messages 64 /// Trait that an application can implement if they want to handle messages
113 /// one at a time. 65 /// one at a time.
150 }) 102 })
151 .collect() 103 .collect()
152 } 104 }
153 } 105 }
154 106
155 /// An owned text response to a PAM conversation.
156 ///
157 /// It points to a value on the C heap.
158 #[repr(C)]
159 struct TextResponse(*mut TextResponseInner);
160
161 impl TextResponse {
162 /// Allocates a new response with the given text.
163 ///
164 /// A copy of the provided text will be allocated on the C heap.
165 pub fn new(text: impl AsRef<str>) -> StdResult<Self, NulError> {
166 TextResponseInner::alloc(text).map(Self)
167 }
168
169 /// Converts this into a GenericResponse.
170 fn generic(self) -> *mut GenericResponse {
171 let ret = self.0 as *mut GenericResponse;
172 mem::forget(self);
173 ret
174 }
175
176 /// Gets the string data, if possible.
177 pub fn as_str(&self) -> StdResult<&str, Utf8Error> {
178 // SAFETY: We allocated this ourselves or got it back from PAM.
179 unsafe { &*self.0 }.contents().to_str()
180 }
181 }
182
183 impl Drop for TextResponse {
184 /// Frees an owned response.
185 fn drop(&mut self) {
186 // SAFETY: We allocated this ourselves, or it was provided by PAM.
187 unsafe { TextResponseInner::free(self.0) }
188 }
189 }
190
191 /// An owned binary response to a PAM conversation.
192 ///
193 /// It points to a value on the C heap.
194 #[repr(C)]
195 pub struct BinaryResponse(pub(super) *mut BinaryResponseInner);
196
197 impl BinaryResponse {
198 /// Creates a binary response with the given data.
199 ///
200 /// A copy of the data will be made and allocated on the C heap.
201 pub fn new(data: &[u8], data_type: u8) -> StdResult<Self, TooBigError> {
202 BinaryResponseInner::alloc(data, data_type).map(Self)
203 }
204
205 /// Converts this into a GenericResponse.
206 fn generic(self) -> *mut GenericResponse {
207 let ret = self.0 as *mut GenericResponse;
208 mem::forget(self);
209 ret
210 }
211
212 /// The data type we point to.
213 pub fn data_type(&self) -> u8 {
214 // SAFETY: We allocated this ourselves or got it back from PAM.
215 unsafe { &*self.0 }.data_type()
216 }
217
218 /// The data we point to.
219 pub fn data(&self) -> &[u8] {
220 // SAFETY: We allocated this ourselves or got it back from PAM.
221 unsafe { &*self.0 }.contents()
222 }
223 }
224
225 impl Drop for BinaryResponse {
226 /// Frees an owned response.
227 fn drop(&mut self) {
228 // SAFETY: We allocated this ourselves, or it was provided by PAM.
229 unsafe { BinaryResponseInner::free(self.0) }
230 }
231 }
232
233 /// Owned binary data. 107 /// Owned binary data.
234 #[derive(Debug, PartialEq)] 108 #[derive(Debug, PartialEq)]
235 pub struct BinaryData { 109 pub struct BinaryData {
236 data: Vec<u8>, 110 data: Vec<u8>,
237 data_type: u8, 111 data_type: u8,
247 pub fn data_type(&self) -> u8 { 121 pub fn data_type(&self) -> u8 {
248 self.data_type 122 self.data_type
249 } 123 }
250 } 124 }
251 125
252 impl From<BinaryResponse> for BinaryData {
253 /// Copies the data onto the Rust heap.
254 fn from(value: BinaryResponse) -> Self {
255 Self {
256 data: value.data().to_vec(),
257 data_type: value.data_type(),
258 }
259 }
260 }
261
262 impl From<BinaryData> for Vec<u8> { 126 impl From<BinaryData> for Vec<u8> {
263 /// Extracts the inner vector from the BinaryData. 127 /// Extracts the inner vector from the BinaryData.
264 fn from(value: BinaryData) -> Self { 128 fn from(value: BinaryData) -> Self {
265 value.data 129 value.data
266 } 130 }
267 } 131 }
268 132
269 #[cfg(test)] 133 #[cfg(test)]
270 mod test { 134 mod tests {
271 use super::{ 135 use super::{Conversation, DemuxedConversation, Message, Response, SecureString};
272 BinaryResponse, Conversation, DemuxedConversation, Message, Response, SecureString,
273 TextResponse,
274 };
275 use crate::constants::ErrorCode; 136 use crate::constants::ErrorCode;
276 use crate::pam_ffi::GenericResponse;
277 137
278 #[test] 138 #[test]
279 fn test_demux() { 139 fn test_demux() {
280 #[derive(Default)] 140 #[derive(Default)]
281 struct DemuxTester { 141 struct DemuxTester {
360 }, 220 },
361 ]) 221 ])
362 .unwrap() 222 .unwrap()
363 ); 223 );
364 } 224 }
365 225 }
366 // The below tests are used in conjunction with ASAN to verify
367 // that we correctly clean up all our memory.
368
369 #[test]
370 fn test_text_response() {
371 let resp = TextResponse::new("it's a-me!").unwrap();
372 assert_eq!("it's a-me!", resp.as_str().unwrap());
373 }
374
375 #[test]
376 fn test_binary_response() {
377 let data = [123, 210, 55];
378 let resp = BinaryResponse::new(&data, 99).unwrap();
379 assert_eq!(data, resp.data());
380 assert_eq!(99, resp.data_type());
381 }
382
383 #[test]
384 fn test_to_generic() {
385 let text = TextResponse::new("oh no").unwrap();
386 let text = text.generic();
387 let binary = BinaryResponse::new(&[], 33).unwrap();
388 let binary = binary.generic();
389 unsafe {
390 GenericResponse::free(text);
391 GenericResponse::free(binary);
392 }
393 }
394 }