Mercurial > crates > nonstick
comparison src/libpam/response.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/response.rs@ac6881304c78 |
| children | 351bdc13005e |
comparison
equal
deleted
inserted
replaced
| 74:c7c596e6388f | 75:c30811b4afae |
|---|---|
| 1 //! Types used when dealing with PAM conversations. | |
| 2 | |
| 3 use crate::conv::BinaryData; | |
| 4 use crate::libpam::memory; | |
| 5 use crate::libpam::memory::{CBinaryData, Immovable, NulError, TooBigError}; | |
| 6 use crate::Response; | |
| 7 use std::ffi::{c_int, c_void, CStr}; | |
| 8 use std::ops::{Deref, DerefMut}; | |
| 9 use std::result::Result as StdResult; | |
| 10 use std::str::Utf8Error; | |
| 11 use std::{iter, mem, ptr, slice}; | |
| 12 | |
| 13 #[repr(transparent)] | |
| 14 #[derive(Debug)] | |
| 15 pub struct RawTextResponse(RawResponse); | |
| 16 | |
| 17 impl RawTextResponse { | |
| 18 /// Interprets the provided `RawResponse` as a text response. | |
| 19 /// | |
| 20 /// # Safety | |
| 21 /// | |
| 22 /// It's up to you to provide a response that is a `RawTextResponse`. | |
| 23 pub unsafe fn upcast(from: &mut RawResponse) -> &mut Self { | |
| 24 // SAFETY: We're provided a valid reference. | |
| 25 &mut *(from as *mut RawResponse).cast::<Self>() | |
| 26 } | |
| 27 | |
| 28 /// Fills in the provided `RawResponse` with the given text. | |
| 29 /// | |
| 30 /// You are responsible for calling [`free`](Self::free_contents) | |
| 31 /// on the pointer you get back when you're done with it. | |
| 32 pub fn fill(dest: &mut RawResponse, text: impl AsRef<str>) -> StdResult<&mut Self, NulError> { | |
| 33 dest.data = memory::malloc_str(text)?.cast(); | |
| 34 // SAFETY: We just filled this in so we know it's a text response. | |
| 35 Ok(unsafe { Self::upcast(dest) }) | |
| 36 } | |
| 37 | |
| 38 /// Gets the string stored in this response. | |
| 39 pub fn contents(&self) -> StdResult<&str, Utf8Error> { | |
| 40 if self.0.data.is_null() { | |
| 41 Ok("") | |
| 42 } else { | |
| 43 // SAFETY: This data is either passed from PAM (so we are forced to | |
| 44 // trust it) or was created by us in TextResponseInner::alloc. | |
| 45 // In either case, it's going to be a valid null-terminated string. | |
| 46 unsafe { CStr::from_ptr(self.0.data.cast()) }.to_str() | |
| 47 } | |
| 48 } | |
| 49 | |
| 50 /// Releases memory owned by this response. | |
| 51 pub fn free_contents(&mut self) { | |
| 52 // SAFETY: We know we own this data. | |
| 53 // After we're done, it will be null. | |
| 54 unsafe { | |
| 55 memory::zero_c_string(self.0.data); | |
| 56 libc::free(self.0.data); | |
| 57 self.0.data = ptr::null_mut() | |
| 58 } | |
| 59 } | |
| 60 } | |
| 61 | |
| 62 /// A [`RawResponse`] with [`CBinaryData`] in it. | |
| 63 #[repr(transparent)] | |
| 64 #[derive(Debug)] | |
| 65 pub struct RawBinaryResponse(RawResponse); | |
| 66 | |
| 67 impl RawBinaryResponse { | |
| 68 /// Interprets the provided `RawResponse` as a binary response. | |
| 69 /// | |
| 70 /// # Safety | |
| 71 /// | |
| 72 /// It's up to you to provide a response that is a `RawBinaryResponse`. | |
| 73 pub unsafe fn upcast(from: &mut RawResponse) -> &mut Self { | |
| 74 // SAFETY: We're provided a valid reference. | |
| 75 &mut *(from as *mut RawResponse).cast::<Self>() | |
| 76 } | |
| 77 | |
| 78 /// Fills in a `RawResponse` with the provided binary data. | |
| 79 /// | |
| 80 /// The `data_type` is a tag you can use for whatever. | |
| 81 /// It is passed through PAM unchanged. | |
| 82 /// | |
| 83 /// The referenced data is copied to the C heap. We do not take ownership. | |
| 84 /// You are responsible for calling [`free`](Self::free_contents) | |
| 85 /// on the pointer you get back when you're done with it. | |
| 86 pub fn fill<'a>( | |
| 87 dest: &'a mut RawResponse, | |
| 88 data: &[u8], | |
| 89 data_type: u8, | |
| 90 ) -> StdResult<&'a mut Self, TooBigError> { | |
| 91 dest.data = CBinaryData::alloc(data, data_type)?.cast(); | |
| 92 // SAFETY: We just filled this in, so we know it's binary. | |
| 93 Ok(unsafe { Self::upcast(dest) }) | |
| 94 } | |
| 95 | |
| 96 /// Gets the binary data in this response. | |
| 97 pub fn data(&self) -> &[u8] { | |
| 98 self.contents().map(CBinaryData::contents).unwrap_or(&[]) | |
| 99 } | |
| 100 | |
| 101 /// Gets the `data_type` tag that was embedded with the message. | |
| 102 pub fn data_type(&self) -> u8 { | |
| 103 self.contents().map(CBinaryData::data_type).unwrap_or(0) | |
| 104 } | |
| 105 | |
| 106 fn contents(&self) -> Option<&CBinaryData> { | |
| 107 // SAFETY: This was either something we got from PAM (in which case | |
| 108 // we trust it), or something that was created with | |
| 109 // BinaryResponseInner::alloc. In both cases, it points to valid data. | |
| 110 unsafe { self.0.data.cast::<CBinaryData>().as_ref() } | |
| 111 } | |
| 112 | |
| 113 pub fn to_owned(&self) -> BinaryData { | |
| 114 BinaryData::new(self.data().into(), self.data_type()) | |
| 115 } | |
| 116 | |
| 117 /// Releases memory owned by this response. | |
| 118 pub fn free_contents(&mut self) { | |
| 119 // SAFETY: We know that our data pointer is either valid or null. | |
| 120 // Once we're done, it's null and the response is safe. | |
| 121 unsafe { | |
| 122 let data_ref = self.0.data.cast::<CBinaryData>().as_mut(); | |
| 123 if let Some(d) = data_ref { | |
| 124 d.zero_contents() | |
| 125 } | |
| 126 libc::free(self.0.data); | |
| 127 self.0.data = ptr::null_mut() | |
| 128 } | |
| 129 } | |
| 130 } | |
| 131 | |
| 132 /// Generic version of response data. | |
| 133 /// | |
| 134 /// This has the same structure as [`RawBinaryResponse`] | |
| 135 /// and [`RawTextResponse`]. | |
| 136 #[repr(C)] | |
| 137 #[derive(Debug)] | |
| 138 pub struct RawResponse { | |
| 139 /// Pointer to the data returned in a response. | |
| 140 /// For most responses, this will be a [`CStr`], but for responses to | |
| 141 /// [`MessageStyle::BinaryPrompt`]s, this will be [`CBinaryData`] | |
| 142 /// (a Linux-PAM extension). | |
| 143 data: *mut c_void, | |
| 144 /// Unused. | |
| 145 return_code: c_int, | |
| 146 _marker: Immovable, | |
| 147 } | |
| 148 | |
| 149 /// A contiguous block of responses. | |
| 150 #[derive(Debug)] | |
| 151 pub struct OwnedResponses { | |
| 152 base: *mut RawResponse, | |
| 153 count: usize, | |
| 154 } | |
| 155 | |
| 156 impl OwnedResponses { | |
| 157 /// Allocates an owned list of responses on the C heap. | |
| 158 fn alloc(count: usize) -> Self { | |
| 159 OwnedResponses { | |
| 160 // SAFETY: We are doing allocation here. | |
| 161 base: unsafe { libc::calloc(count, size_of::<RawResponse>()) }.cast(), | |
| 162 count, | |
| 163 } | |
| 164 } | |
| 165 | |
| 166 pub fn build(value: &[Response]) -> StdResult<Self, FillError> { | |
| 167 let mut outputs = OwnedResponses::alloc(value.len()); | |
| 168 // If we fail in here after allocating OwnedResponses, | |
| 169 // we still free all memory, even though we don't zero it first. | |
| 170 // This is an acceptable level of risk. | |
| 171 for (input, output) in iter::zip(value.iter(), outputs.iter_mut()) { | |
| 172 match input { | |
| 173 Response::NoResponse => { | |
| 174 RawTextResponse::fill(output, "")?; | |
| 175 } | |
| 176 Response::Text(data) => { | |
| 177 RawTextResponse::fill(output, data)?; | |
| 178 } | |
| 179 Response::MaskedText(data) => { | |
| 180 RawTextResponse::fill(output, data.unsecure())?; | |
| 181 } | |
| 182 Response::Binary(data) => { | |
| 183 RawBinaryResponse::fill(output, data.data(), data.data_type())?; | |
| 184 } | |
| 185 } | |
| 186 } | |
| 187 Ok(outputs) | |
| 188 } | |
| 189 | |
| 190 /// Converts this into a `*RawResponse` for passing to PAM. | |
| 191 /// | |
| 192 /// The pointer "owns" its own data (i.e., this will not be dropped). | |
| 193 pub fn into_ptr(self) -> *mut RawResponse { | |
| 194 let ret = self.base; | |
| 195 mem::forget(self); | |
| 196 ret | |
| 197 } | |
| 198 | |
| 199 /// Takes ownership of a list of responses allocated on the C heap. | |
| 200 /// | |
| 201 /// # Safety | |
| 202 /// | |
| 203 /// It's up to you to make sure you pass a valid pointer. | |
| 204 pub unsafe fn from_c_heap(base: *mut RawResponse, count: usize) -> Self { | |
| 205 OwnedResponses { base, count } | |
| 206 } | |
| 207 } | |
| 208 | |
| 209 #[derive(Debug, thiserror::Error)] | |
| 210 #[error("error converting responses: {0}")] | |
| 211 pub enum FillError { | |
| 212 NulError(#[from] NulError), | |
| 213 TooBigError(#[from] TooBigError), | |
| 214 } | |
| 215 | |
| 216 impl Deref for OwnedResponses { | |
| 217 type Target = [RawResponse]; | |
| 218 fn deref(&self) -> &Self::Target { | |
| 219 // SAFETY: This is the memory we manage ourselves. | |
| 220 unsafe { slice::from_raw_parts(self.base, self.count) } | |
| 221 } | |
| 222 } | |
| 223 | |
| 224 impl DerefMut for OwnedResponses { | |
| 225 fn deref_mut(&mut self) -> &mut Self::Target { | |
| 226 // SAFETY: This is the memory we manage ourselves. | |
| 227 unsafe { slice::from_raw_parts_mut(self.base, self.count) } | |
| 228 } | |
| 229 } | |
| 230 | |
| 231 impl Drop for OwnedResponses { | |
| 232 fn drop(&mut self) { | |
| 233 // SAFETY: We allocated this ourselves, or it was provided to us by PAM. | |
| 234 unsafe { | |
| 235 for resp in self.iter_mut() { | |
| 236 libc::free(resp.data) | |
| 237 } | |
| 238 libc::free(self.base.cast()) | |
| 239 } | |
| 240 } | |
| 241 } | |
| 242 | |
| 243 #[cfg(test)] | |
| 244 mod tests { | |
| 245 use super::{BinaryData, OwnedResponses, RawBinaryResponse, RawTextResponse, Response}; | |
| 246 | |
| 247 #[test] | |
| 248 fn test_round_trip() { | |
| 249 let responses = [ | |
| 250 Response::Binary(BinaryData::new(vec![1, 2, 3], 99)), | |
| 251 Response::Text("whats going on".to_owned()), | |
| 252 Response::MaskedText("well then".into()), | |
| 253 Response::NoResponse, | |
| 254 Response::Text("bogus".to_owned()), | |
| 255 ]; | |
| 256 let sent = OwnedResponses::build(&responses).unwrap(); | |
| 257 let heap_resps = sent.into_ptr(); | |
| 258 let mut received = unsafe { OwnedResponses::from_c_heap(heap_resps, 5) }; | |
| 259 | |
| 260 let assert_text = |want, raw| { | |
| 261 let up = unsafe { RawTextResponse::upcast(raw) }; | |
| 262 assert_eq!(want, up.contents().unwrap()); | |
| 263 up.free_contents(); | |
| 264 assert_eq!("", up.contents().unwrap()); | |
| 265 }; | |
| 266 let assert_bin = |want_data: &[u8], want_type, raw| { | |
| 267 let up = unsafe { RawBinaryResponse::upcast(raw) }; | |
| 268 assert_eq!(want_data, up.data()); | |
| 269 assert_eq!(want_type, up.data_type()); | |
| 270 up.free_contents(); | |
| 271 let empty: [u8; 0] = []; | |
| 272 assert_eq!(&empty, up.data()); | |
| 273 assert_eq!(0, up.data_type()); | |
| 274 }; | |
| 275 if let [zero, one, two, three, four] = &mut received[..] { | |
| 276 assert_bin(&[1, 2, 3], 99, zero); | |
| 277 assert_text("whats going on", one); | |
| 278 assert_text("well then", two); | |
| 279 assert_text("", three); | |
| 280 assert_text("bogus", four); | |
| 281 } else { | |
| 282 panic!("wrong size!") | |
| 283 } | |
| 284 } | |
| 285 | |
| 286 #[test] | |
| 287 fn test_text_response() { | |
| 288 let mut responses = OwnedResponses::alloc(2); | |
| 289 let text = RawTextResponse::fill(&mut responses[0], "hello").unwrap(); | |
| 290 let data = text.contents().expect("valid"); | |
| 291 assert_eq!("hello", data); | |
| 292 text.free_contents(); | |
| 293 text.free_contents(); | |
| 294 RawTextResponse::fill(&mut responses[1], "hell\0").expect_err("should error; contains nul"); | |
| 295 } | |
| 296 | |
| 297 #[test] | |
| 298 fn test_binary_response() { | |
| 299 let mut responses = OwnedResponses::alloc(1); | |
| 300 let real_data = [1, 2, 3, 4, 5, 6, 7, 8]; | |
| 301 let resp = RawBinaryResponse::fill(&mut responses[0], &real_data, 7) | |
| 302 .expect("alloc should succeed"); | |
| 303 let data = resp.data(); | |
| 304 assert_eq!(&real_data, data); | |
| 305 assert_eq!(7, resp.data_type()); | |
| 306 resp.free_contents(); | |
| 307 resp.free_contents(); | |
| 308 } | |
| 309 | |
| 310 #[test] | |
| 311 #[ignore] | |
| 312 fn test_binary_response_too_big() { | |
| 313 let big_data: Vec<u8> = vec![0xFFu8; 10_000_000_000]; | |
| 314 let mut responses = OwnedResponses::alloc(1); | |
| 315 RawBinaryResponse::fill(&mut responses[0], &big_data, 0).expect_err("this is too big!"); | |
| 316 } | |
| 317 } |
