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 }