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