comparison src/libpam/response.rs @ 77:351bdc13005e

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