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 } |
