Mercurial > crates > nonstick
comparison src/conv.rs @ 69:8f3ae0c7ab92
Rework conversation data types and make safe wrappers.
This removes the old `Conversation` type and reworks the FFI types
used for PAM conversations.
This creates safe `TestResponse` and `BinaryResponse` structures in `conv`,
providing a safe way to pass response messages to PAM Conversations.
The internals of these types are allocated on the C heap, as required by PAM.
We also remove the Conversation struct, which was specific to the real PAM
implementation so that we can introduce a better abstraction.
Also splits a new `PamApplicationHandle` trait from `PamHandle`,
for the parts of a PAM handle that are specific to the application side
of a PAM transaction.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sun, 01 Jun 2025 01:15:04 -0400 |
parents | bbe84835d6db |
children | 9f8381a1c09c |
comparison
equal
deleted
inserted
replaced
68:e4e7d68234d0 | 69:8f3ae0c7ab92 |
---|---|
1 //! The [Conversation] struct, for interacting with the user. | 1 //! The PAM conversation and associated Stuff. |
2 //! | |
3 //! This module is experimental and will probably be rewritten in the future | |
4 //! to improve the interface for both PAM modules and clients. | |
5 | 2 |
6 use crate::constants::MessageStyle; | 3 use crate::pam_ffi::{BinaryResponseInner, NulError, TextResponseInner}; |
7 use crate::constants::Result; | 4 use std::num::TryFromIntError; |
8 use crate::constants::{ErrorCode, InvalidEnum}; | 5 use std::ops::Deref; |
9 use crate::items::Item; | 6 use std::result::Result as StdResult; |
10 use libc::{c_char, c_int}; | |
11 use num_derive::FromPrimitive; | |
12 use std::ffi::{CStr, CString}; | |
13 use std::ptr; | |
14 | 7 |
15 /// Styles of message that are shown to the user. | 8 /// An owned text response to a PAM conversation. |
16 #[derive(Debug, PartialEq, FromPrimitive)] | 9 /// |
17 #[non_exhaustive] // non-exhaustive because C might give us back anything! | 10 /// It points to a value on the C heap. |
18 pub enum MessageStyle { | 11 #[repr(C)] |
19 /// Requests information from the user; will be masked when typing. | 12 struct TextResponse(*mut TextResponseInner); |
20 PromptEchoOff = 1, | |
21 /// Requests information from the user; will not be masked. | |
22 PromptEchoOn = 2, | |
23 /// An error message. | |
24 ErrorMsg = 3, | |
25 /// An informational message. | |
26 TextInfo = 4, | |
27 /// Yes/No/Maybe conditionals. Linux-PAM specific. | |
28 RadioType = 5, | |
29 /// For server–client non-human interaction. | |
30 /// NOT part of the X/Open PAM specification. | |
31 BinaryPrompt = 7, | |
32 } | |
33 | 13 |
34 impl TryFrom<c_int> for MessageStyle { | 14 impl TextResponse { |
35 type Error = InvalidEnum<Self>; | 15 /// Creates a text response. |
36 fn try_from(value: c_int) -> std::result::Result<Self, Self::Error> { | 16 pub fn new(text: impl AsRef<str>) -> StdResult<Self, NulError> { |
37 Self::from_i32(value).ok_or(value.into()) | 17 TextResponseInner::alloc(text).map(Self) |
38 } | 18 } |
39 } | 19 } |
40 | 20 |
41 impl From<MessageStyle> for c_int { | 21 impl Deref for TextResponse { |
42 fn from(val: MessageStyle) -> Self { | 22 type Target = TextResponseInner; |
43 val as Self | 23 fn deref(&self) -> &Self::Target { |
24 // SAFETY: We allocated this ourselves, or it was provided by PAM. | |
25 unsafe { &*self.0 } | |
44 } | 26 } |
45 } | 27 } |
46 | 28 |
47 #[repr(C)] | 29 impl Drop for TextResponse { |
48 struct Message { | 30 /// Frees an owned response. |
49 msg_style: MessageStyle, | 31 fn drop(&mut self) { |
50 msg: *const c_char, | 32 // SAFETY: We allocated this ourselves, or it was provided by PAM. |
51 } | 33 unsafe { TextResponseInner::free(self.0) } |
52 | |
53 #[repr(C)] | |
54 struct Response { | |
55 resp: *const c_char, | |
56 resp_retcode: libc::c_int, // Unused - always zero | |
57 } | |
58 | |
59 #[doc(hidden)] | |
60 #[repr(C)] | |
61 pub struct Inner { | |
62 conv: extern "C" fn( | |
63 num_msg: c_int, | |
64 pam_message: &&Message, | |
65 pam_response: &mut *const Response, | |
66 appdata_ptr: *const libc::c_void, | |
67 ) -> c_int, | |
68 appdata_ptr: *const libc::c_void, | |
69 } | |
70 | |
71 /// A communication channel with the user. | |
72 /// | |
73 /// Use this to communicate with the user, if needed, beyond the standard | |
74 /// things you can get/set with `get_user`/`get_authtok` and friends. | |
75 /// The PAM client (i.e., the application that is logging in) will present | |
76 /// the messages you send to the user and ask for responses. | |
77 pub struct Conversation<'a>(&'a Inner); | |
78 | |
79 impl Conversation<'_> { | |
80 /// Sends a message to the PAM client. | |
81 /// | |
82 /// This will typically result in the user seeing a message or a prompt. | |
83 /// For details, see what [MessageStyle]s are available. | |
84 /// | |
85 /// Note that the user experience will depend on how each style | |
86 /// is implemented by the client, and that not all clients | |
87 /// will implement all message styles. | |
88 pub fn send(&self, style: MessageStyle, msg: &str) -> Result<Option<&CStr>> { | |
89 let mut resp_ptr: *const Response = ptr::null(); | |
90 let msg_cstr = CString::new(msg).unwrap(); | |
91 let msg = Message { | |
92 msg_style: style, | |
93 msg: msg_cstr.as_ptr(), | |
94 }; | |
95 // TODO: These need to be freed! | |
96 let ret = (self.0.conv)(1, &&msg, &mut resp_ptr, self.0.appdata_ptr); | |
97 ErrorCode::result_from(ret)?; | |
98 | |
99 let result = unsafe { | |
100 match (*resp_ptr).resp { | |
101 p if p.is_null() => None, | |
102 p => Some(CStr::from_ptr(p)), | |
103 } | |
104 }; | |
105 Ok(result) | |
106 } | 34 } |
107 } | 35 } |
108 | 36 |
109 impl Item for Conversation<'_> { | 37 /// An owned binary response to a PAM conversation. |
110 type Raw = Inner; | 38 /// |
39 /// It points to a value on the C heap. | |
40 #[repr(C)] | |
41 struct BinaryResponse(*mut BinaryResponseInner); | |
111 | 42 |
112 fn type_id() -> crate::items::ItemType { | 43 impl BinaryResponse { |
113 crate::items::ItemType::Conversation | 44 /// Creates a binary response with the given data. |
114 } | 45 pub fn new(data: impl AsRef<[u8]>, data_type: u8) -> StdResult<Self, TryFromIntError> { |
115 | 46 BinaryResponseInner::alloc(data, data_type).map(Self) |
116 unsafe fn from_raw(raw: *const Self::Raw) -> Self { | |
117 Self(&*raw) | |
118 } | |
119 | |
120 fn into_raw(self) -> *const Self::Raw { | |
121 self.0 as _ | |
122 } | 47 } |
123 } | 48 } |
49 | |
50 impl Deref for BinaryResponse { | |
51 type Target = BinaryResponseInner; | |
52 fn deref(&self) -> &Self::Target { | |
53 // SAFETY: We allocated this ourselves, or it was provided by PAM. | |
54 unsafe { &*self.0 } | |
55 } | |
56 } | |
57 | |
58 impl Drop for BinaryResponse { | |
59 /// Frees an owned response. | |
60 fn drop(&mut self) { | |
61 // SAFETY: We allocated this ourselves, or it was provided by PAM. | |
62 unsafe { BinaryResponseInner::free(self.0) } | |
63 } | |
64 } | |
65 | |
66 #[cfg(test)] | |
67 mod test { | |
68 use super::{BinaryResponse, TextResponse}; | |
69 | |
70 #[test] | |
71 fn test_text_response() { | |
72 let resp = TextResponse::new("it's a-me!").unwrap(); | |
73 assert_eq!("it's a-me!", resp.contents().to_str().unwrap()); | |
74 } | |
75 #[test] | |
76 fn test_binary_response() { | |
77 let data = [123, 210, 55]; | |
78 let resp = BinaryResponse::new(&data, 99).unwrap(); | |
79 assert_eq!(&data, resp.contents()); | |
80 } | |
81 } |