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