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 }