Mercurial > crates > nonstick
comparison src/libpam/message.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/message.rs@c7c596e6388f |
| children | 351bdc13005e |
comparison
equal
deleted
inserted
replaced
| 74:c7c596e6388f | 75:c30811b4afae |
|---|---|
| 1 //! Data and types dealing with PAM messages. | |
| 2 | |
| 3 use crate::constants::InvalidEnum; | |
| 4 use crate::conv::Message; | |
| 5 use crate::libpam::memory; | |
| 6 use crate::libpam::memory::{CBinaryData, Immovable, NulError, TooBigError}; | |
| 7 use num_derive::FromPrimitive; | |
| 8 use num_traits::FromPrimitive; | |
| 9 use std::ffi::{c_int, c_void, CStr}; | |
| 10 use std::result::Result as StdResult; | |
| 11 use std::str::Utf8Error; | |
| 12 use std::{ptr, slice}; | |
| 13 | |
| 14 #[derive(Debug, thiserror::Error)] | |
| 15 #[error("error creating PAM message: {0}")] | |
| 16 pub enum ConversionError { | |
| 17 InvalidEnum(#[from] InvalidEnum<Style>), | |
| 18 Utf8Error(#[from] Utf8Error), | |
| 19 NulError(#[from] NulError), | |
| 20 TooBigError(#[from] TooBigError), | |
| 21 } | |
| 22 | |
| 23 /// The C enum values for messages shown to the user. | |
| 24 #[derive(Debug, PartialEq, FromPrimitive)] | |
| 25 pub enum Style { | |
| 26 /// Requests information from the user; will be masked when typing. | |
| 27 PromptEchoOff = 1, | |
| 28 /// Requests information from the user; will not be masked. | |
| 29 PromptEchoOn = 2, | |
| 30 /// An error message. | |
| 31 ErrorMsg = 3, | |
| 32 /// An informational message. | |
| 33 TextInfo = 4, | |
| 34 /// Yes/No/Maybe conditionals. A Linux-PAM extension. | |
| 35 RadioType = 5, | |
| 36 /// For server–client non-human interaction. | |
| 37 /// | |
| 38 /// NOT part of the X/Open PAM specification. | |
| 39 /// A Linux-PAM extension. | |
| 40 BinaryPrompt = 7, | |
| 41 } | |
| 42 | |
| 43 impl TryFrom<c_int> for Style { | |
| 44 type Error = InvalidEnum<Self>; | |
| 45 fn try_from(value: c_int) -> StdResult<Self, Self::Error> { | |
| 46 Self::from_i32(value).ok_or(value.into()) | |
| 47 } | |
| 48 } | |
| 49 | |
| 50 impl From<Style> for c_int { | |
| 51 fn from(val: Style) -> Self { | |
| 52 val as Self | |
| 53 } | |
| 54 } | |
| 55 | |
| 56 /// A message sent by PAM or a module to an application. | |
| 57 /// This message, and its internal data, is owned by the creator | |
| 58 /// (either the module or PAM itself). | |
| 59 #[repr(C)] | |
| 60 pub struct RawMessage { | |
| 61 /// The style of message to request. | |
| 62 style: c_int, | |
| 63 /// A description of the data requested. | |
| 64 /// | |
| 65 /// For most requests, this will be an owned [`CStr`], but for requests | |
| 66 /// with [`Style::BinaryPrompt`], this will be [`CBinaryData`] | |
| 67 /// (a Linux-PAM extension). | |
| 68 data: *mut c_void, | |
| 69 _marker: Immovable, | |
| 70 } | |
| 71 | |
| 72 impl RawMessage { | |
| 73 pub fn set(&mut self, msg: Message) -> StdResult<(), ConversionError> { | |
| 74 let (style, data) = copy_to_heap(msg)?; | |
| 75 self.clear(); | |
| 76 // SAFETY: We allocated this ourselves or were given it by PAM. | |
| 77 // Otherwise, it's null, but free(null) is fine. | |
| 78 unsafe { libc::free(self.data) }; | |
| 79 self.style = style as c_int; | |
| 80 self.data = data; | |
| 81 Ok(()) | |
| 82 } | |
| 83 | |
| 84 /// Gets this message's data pointer as a string. | |
| 85 /// | |
| 86 /// # Safety | |
| 87 /// | |
| 88 /// It's up to you to pass this only on types with a string value. | |
| 89 unsafe fn string_data(&self) -> StdResult<&str, Utf8Error> { | |
| 90 if self.data.is_null() { | |
| 91 Ok("") | |
| 92 } else { | |
| 93 CStr::from_ptr(self.data.cast()).to_str() | |
| 94 } | |
| 95 } | |
| 96 | |
| 97 /// Zeroes out the data stored here. | |
| 98 fn clear(&mut self) { | |
| 99 // SAFETY: We either created this data or we got it from PAM. | |
| 100 // After this function is done, it will be zeroed out. | |
| 101 unsafe { | |
| 102 if let Ok(style) = Style::try_from(self.style) { | |
| 103 match style { | |
| 104 Style::BinaryPrompt => { | |
| 105 if let Some(d) = self.data.cast::<CBinaryData>().as_mut() { | |
| 106 d.zero_contents() | |
| 107 } | |
| 108 } | |
| 109 Style::TextInfo | |
| 110 | Style::RadioType | |
| 111 | Style::ErrorMsg | |
| 112 | Style::PromptEchoOff | |
| 113 | Style::PromptEchoOn => memory::zero_c_string(self.data), | |
| 114 } | |
| 115 }; | |
| 116 libc::free(self.data); | |
| 117 self.data = ptr::null_mut(); | |
| 118 } | |
| 119 } | |
| 120 } | |
| 121 | |
| 122 /// Copies the contents of this message to the C heap. | |
| 123 fn copy_to_heap(msg: Message) -> StdResult<(Style, *mut c_void), ConversionError> { | |
| 124 let alloc = |style, text| Ok((style, memory::malloc_str(text)?.cast())); | |
| 125 match msg { | |
| 126 Message::MaskedPrompt(text) => alloc(Style::PromptEchoOff, text), | |
| 127 Message::Prompt(text) => alloc(Style::PromptEchoOn, text), | |
| 128 Message::RadioPrompt(text) => alloc(Style::RadioType, text), | |
| 129 Message::ErrorMsg(text) => alloc(Style::ErrorMsg, text), | |
| 130 Message::InfoMsg(text) => alloc(Style::TextInfo, text), | |
| 131 Message::BinaryPrompt { data, data_type } => Ok(( | |
| 132 Style::BinaryPrompt, | |
| 133 (CBinaryData::alloc(data, data_type)?).cast(), | |
| 134 )), | |
| 135 } | |
| 136 } | |
| 137 | |
| 138 /// Abstraction of a list-of-messages to be sent in a PAM conversation. | |
| 139 /// | |
| 140 /// On Linux-PAM and other compatible implementations, `messages` | |
| 141 /// is treated as a pointer-to-pointers, like `int argc, char **argv`. | |
| 142 /// (In this situation, the value of `OwnedMessages.indirect` is | |
| 143 /// the pointer passed to `pam_conv`.) | |
| 144 /// | |
| 145 /// ```text | |
| 146 /// ╔═ OwnedMsgs ═╗ points to ┌─ Indirect ─┐ ╔═ Message ═╗ | |
| 147 /// ║ indirect ┄┄┄╫┄┄┄┄┄┄┄┄┄┄┄> │ base[0] ┄┄┄┼┄┄┄┄┄> ║ style ║ | |
| 148 /// ║ count ║ │ base[1] ┄┄┄┼┄┄┄╮ ║ data ║ | |
| 149 /// ╚═════════════╝ │ ... │ ┆ ╚═══════════╝ | |
| 150 /// ┆ | |
| 151 /// ┆ ╔═ Message ═╗ | |
| 152 /// ╰┄┄> ║ style ║ | |
| 153 /// ║ data ║ | |
| 154 /// ╚═══════════╝ | |
| 155 /// ``` | |
| 156 /// | |
| 157 /// On OpenPAM and other compatible implementations (like Solaris), | |
| 158 /// `messages` is a pointer-to-pointer-to-array. This appears to be | |
| 159 /// the correct implementation as required by the XSSO specification. | |
| 160 /// | |
| 161 /// ```text | |
| 162 /// ╔═ OwnedMsgs ═╗ points to ┌─ Indirect ─┐ ╔═ Message[] ═╗ | |
| 163 /// ║ indirect ┄┄┄╫┄┄┄┄┄┄┄┄┄┄┄> │ base ┄┄┄┄┄┄┼┄┄┄┄┄> ║ style ║ | |
| 164 /// ║ count ║ └────────────┘ ║ data ║ | |
| 165 /// ╚═════════════╝ ╟─────────────╢ | |
| 166 /// ║ style ║ | |
| 167 /// ║ data ║ | |
| 168 /// ╟─────────────╢ | |
| 169 /// ║ ... ║ | |
| 170 /// ``` | |
| 171 /// | |
| 172 /// ***THIS LIBRARY CURRENTLY SUPPORTS ONLY LINUX-PAM.*** | |
| 173 pub struct OwnedMessages { | |
| 174 /// An indirection to the messages themselves, stored on the C heap. | |
| 175 indirect: *mut MessageIndirector, | |
| 176 /// The number of messages in the list. | |
| 177 count: usize, | |
| 178 } | |
| 179 | |
| 180 impl OwnedMessages { | |
| 181 /// Allocates data to store messages on the C heap. | |
| 182 pub fn alloc(count: usize) -> Self { | |
| 183 Self { | |
| 184 indirect: MessageIndirector::alloc(count), | |
| 185 count, | |
| 186 } | |
| 187 } | |
| 188 | |
| 189 /// The pointer to the thing with the actual list. | |
| 190 pub fn indirector(&self) -> *const MessageIndirector { | |
| 191 self.indirect | |
| 192 } | |
| 193 | |
| 194 pub fn iter(&self) -> impl Iterator<Item = &RawMessage> { | |
| 195 // SAFETY: we're iterating over an amount we know. | |
| 196 unsafe { (*self.indirect).iter(self.count) } | |
| 197 } | |
| 198 | |
| 199 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut RawMessage> { | |
| 200 // SAFETY: we're iterating over an amount we know. | |
| 201 unsafe { (*self.indirect).iter_mut(self.count) } | |
| 202 } | |
| 203 } | |
| 204 | |
| 205 impl Drop for OwnedMessages { | |
| 206 fn drop(&mut self) { | |
| 207 // SAFETY: We are valid and have a valid pointer. | |
| 208 // Once we're done, everything will be safe. | |
| 209 unsafe { | |
| 210 if let Some(indirect) = self.indirect.as_mut() { | |
| 211 indirect.free(self.count) | |
| 212 } | |
| 213 libc::free(self.indirect.cast()); | |
| 214 self.indirect = ptr::null_mut(); | |
| 215 } | |
| 216 } | |
| 217 } | |
| 218 | |
| 219 /// An indirect reference to messages. | |
| 220 /// | |
| 221 /// This is kept separate to provide a place where we can separate | |
| 222 /// the pointer-to-pointer-to-list from pointer-to-list-of-pointers. | |
| 223 #[repr(transparent)] | |
| 224 pub struct MessageIndirector { | |
| 225 base: [*mut RawMessage; 0], | |
| 226 _marker: Immovable, | |
| 227 } | |
| 228 | |
| 229 impl MessageIndirector { | |
| 230 /// Allocates memory for this indirector and all its members. | |
| 231 fn alloc(count: usize) -> *mut Self { | |
| 232 // SAFETY: We're only allocating, and when we're done, | |
| 233 // everything will be in a known-good state. | |
| 234 unsafe { | |
| 235 let me_ptr: *mut MessageIndirector = | |
| 236 libc::calloc(count, size_of::<*mut RawMessage>()).cast(); | |
| 237 let me = &mut *me_ptr; | |
| 238 let ptr_list = slice::from_raw_parts_mut(me.base.as_mut_ptr(), count); | |
| 239 for entry in ptr_list { | |
| 240 *entry = libc::calloc(1, size_of::<RawMessage>()).cast(); | |
| 241 } | |
| 242 me | |
| 243 } | |
| 244 } | |
| 245 | |
| 246 /// Returns an iterator yielding the given number of messages. | |
| 247 /// | |
| 248 /// # Safety | |
| 249 /// | |
| 250 /// You have to provide the right count. | |
| 251 pub unsafe fn iter(&self, count: usize) -> impl Iterator<Item = &RawMessage> { | |
| 252 (0..count).map(|idx| &**self.base.as_ptr().add(idx)) | |
| 253 } | |
| 254 | |
| 255 /// Returns a mutable iterator yielding the given number of messages. | |
| 256 /// | |
| 257 /// # Safety | |
| 258 /// | |
| 259 /// You have to provide the right count. | |
| 260 pub unsafe fn iter_mut(&mut self, count: usize) -> impl Iterator<Item = &mut RawMessage> { | |
| 261 (0..count).map(|idx| &mut **self.base.as_mut_ptr().add(idx)) | |
| 262 } | |
| 263 | |
| 264 /// Frees this and everything it points to. | |
| 265 /// | |
| 266 /// # Safety | |
| 267 /// | |
| 268 /// You have to pass the right size. | |
| 269 unsafe fn free(&mut self, count: usize) { | |
| 270 let msgs = slice::from_raw_parts_mut(self.base.as_mut_ptr(), count); | |
| 271 for msg in msgs { | |
| 272 if let Some(msg) = msg.as_mut() { | |
| 273 msg.clear(); | |
| 274 } | |
| 275 libc::free(msg.cast()); | |
| 276 *msg = ptr::null_mut(); | |
| 277 } | |
| 278 } | |
| 279 } | |
| 280 | |
| 281 impl<'a> TryFrom<&'a RawMessage> for Message<'a> { | |
| 282 type Error = ConversionError; | |
| 283 | |
| 284 /// Retrieves the data stored in this message. | |
| 285 fn try_from(input: &RawMessage) -> StdResult<Message, ConversionError> { | |
| 286 let style: Style = input.style.try_into()?; | |
| 287 // SAFETY: We either allocated this message ourselves or were provided it by PAM. | |
| 288 let result = unsafe { | |
| 289 match style { | |
| 290 Style::PromptEchoOff => Message::MaskedPrompt(input.string_data()?), | |
| 291 Style::PromptEchoOn => Message::Prompt(input.string_data()?), | |
| 292 Style::TextInfo => Message::InfoMsg(input.string_data()?), | |
| 293 Style::ErrorMsg => Message::ErrorMsg(input.string_data()?), | |
| 294 Style::RadioType => Message::ErrorMsg(input.string_data()?), | |
| 295 Style::BinaryPrompt => input.data.cast::<CBinaryData>().as_ref().map_or_else( | |
| 296 || Message::BinaryPrompt { | |
| 297 data_type: 0, | |
| 298 data: &[], | |
| 299 }, | |
| 300 |data| Message::BinaryPrompt { | |
| 301 data_type: data.data_type(), | |
| 302 data: data.contents(), | |
| 303 }, | |
| 304 ), | |
| 305 } | |
| 306 }; | |
| 307 Ok(result) | |
| 308 } | |
| 309 } | |
| 310 | |
| 311 #[cfg(test)] | |
| 312 mod tests { | |
| 313 use crate::conv::Message; | |
| 314 use crate::libpam::message::OwnedMessages; | |
| 315 | |
| 316 #[test] | |
| 317 fn test_owned_messages() { | |
| 318 let mut tons_of_messages = OwnedMessages::alloc(10); | |
| 319 let mut msgs: Vec<_> = tons_of_messages.iter_mut().collect(); | |
| 320 assert!(msgs.get(10).is_none()); | |
| 321 let last_msg = &mut msgs[9]; | |
| 322 last_msg.set(Message::MaskedPrompt("hocus pocus")).unwrap(); | |
| 323 let another_msg = &mut msgs[0]; | |
| 324 another_msg | |
| 325 .set(Message::BinaryPrompt { | |
| 326 data: &[5, 4, 3, 2, 1], | |
| 327 data_type: 99, | |
| 328 }) | |
| 329 .unwrap(); | |
| 330 let overwrite = &mut msgs[3]; | |
| 331 overwrite.set(Message::Prompt("what")).unwrap(); | |
| 332 overwrite.set(Message::Prompt("who")).unwrap(); | |
| 333 } | |
| 334 } |
