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