Mercurial > crates > nonstick
comparison src/libpam/handle.rs @ 80:5aa1a010f1e8
Start using PAM headers; improve owned/borrowed distinction.
- Uses bindgen to generate bindings (only if needed).
- Gets the story together on owned vs. borrowed handles.
- Reduces number of mutable borrows in handle operation
(since `PamHandle` is neither `Send` nor `Sync`,
we never have to worry about thread safety.
- Improves a bunch of macros so we don't have our own
special syntax for docs.
- Implement question indirection for standard XSSO PAM implementations.
| author | Paul Fisher <paul@pfish.zone> |
|---|---|
| date | Tue, 10 Jun 2025 01:09:30 -0400 |
| parents | 002adfb98c5c |
| children | f6186e41399b |
comparison
equal
deleted
inserted
replaced
| 79:2128123b9406 | 80:5aa1a010f1e8 |
|---|---|
| 1 use super::conversation::LibPamConversation; | 1 use super::conversation::LibPamConversation; |
| 2 use crate::constants::{ErrorCode, InvalidEnum, Result}; | 2 use crate::constants::{ErrorCode, Result}; |
| 3 use crate::conv::Message; | 3 use crate::conv::Message; |
| 4 use crate::handle::{PamApplicationOnly, PamModuleOnly, PamShared}; | 4 use crate::handle::PamShared; |
| 5 use crate::libpam::memory; | 5 pub use crate::libpam::pam_ffi::LibPamHandle; |
| 6 use crate::libpam::memory::Immovable; | 6 use crate::libpam::{memory, pam_ffi}; |
| 7 use crate::Conversation; | 7 use crate::{Conversation, PamHandleModule}; |
| 8 use num_derive::FromPrimitive; | 8 use num_enum::{IntoPrimitive, TryFromPrimitive}; |
| 9 use num_traits::FromPrimitive; | 9 use std::cell::Cell; |
| 10 use std::ffi::{c_char, c_int}; | 10 use std::ffi::{c_char, c_int}; |
| 11 use std::ops::{Deref, DerefMut}; | 11 use std::ops::{Deref, DerefMut}; |
| 12 use std::result::Result as StdResult; | 12 use std::ptr; |
| 13 use std::{mem, ptr}; | 13 |
| 14 | 14 struct HandleWrap(*mut LibPamHandle); |
| 15 /// An owned PAM handle. | 15 |
| 16 #[repr(transparent)] | 16 impl Deref for HandleWrap { |
| 17 pub struct OwnedLibPamHandle(*mut LibPamHandle); | |
| 18 | |
| 19 /// An opaque structure that a PAM handle points to. | |
| 20 #[repr(C)] | |
| 21 pub struct LibPamHandle { | |
| 22 _data: (), | |
| 23 _marker: Immovable, | |
| 24 } | |
| 25 | |
| 26 impl LibPamHandle { | |
| 27 /// Gets a C string item. | |
| 28 /// | |
| 29 /// # Safety | |
| 30 /// | |
| 31 /// You better be requesting an item which is a C string. | |
| 32 unsafe fn get_cstr_item(&mut self, item_type: ItemType) -> Result<Option<&str>> { | |
| 33 let mut output = ptr::null(); | |
| 34 let ret = unsafe { super::pam_get_item(self, item_type as c_int, &mut output) }; | |
| 35 ErrorCode::result_from(ret)?; | |
| 36 memory::wrap_string(output.cast()) | |
| 37 } | |
| 38 | |
| 39 /// Sets a C string item. | |
| 40 /// | |
| 41 /// # Safety | |
| 42 /// | |
| 43 /// You better be setting an item which is a C string. | |
| 44 unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&str>) -> Result<()> { | |
| 45 let data_str = memory::option_cstr(data)?; | |
| 46 let ret = unsafe { | |
| 47 super::pam_set_item( | |
| 48 self, | |
| 49 item_type as c_int, | |
| 50 memory::prompt_ptr(data_str.as_ref()).cast(), | |
| 51 ) | |
| 52 }; | |
| 53 ErrorCode::result_from(ret) | |
| 54 } | |
| 55 | |
| 56 /// Gets the `PAM_CONV` item from the handle. | |
| 57 fn conversation_item(&mut self) -> Result<&mut LibPamConversation<'_>> { | |
| 58 let output: *mut LibPamConversation = ptr::null_mut(); | |
| 59 let result = unsafe { | |
| 60 super::pam_get_item( | |
| 61 self, | |
| 62 ItemType::Conversation.into(), | |
| 63 &mut output.cast_const().cast(), | |
| 64 ) | |
| 65 }; | |
| 66 ErrorCode::result_from(result)?; | |
| 67 // SAFETY: We got this result from PAM, and we're checking if it's null. | |
| 68 unsafe { output.as_mut() }.ok_or(ErrorCode::ConversationError) | |
| 69 } | |
| 70 } | |
| 71 | |
| 72 impl PamApplicationOnly for OwnedLibPamHandle { | |
| 73 fn close(self, status: Result<()>) -> Result<()> { | |
| 74 let ret = unsafe { super::pam_end(self.0, ErrorCode::result_to_c(status)) }; | |
| 75 // Forget rather than dropping, since dropping also calls pam_end. | |
| 76 mem::forget(self); | |
| 77 ErrorCode::result_from(ret) | |
| 78 } | |
| 79 } | |
| 80 | |
| 81 impl Deref for OwnedLibPamHandle { | |
| 82 type Target = LibPamHandle; | 17 type Target = LibPamHandle; |
| 83 fn deref(&self) -> &Self::Target { | 18 fn deref(&self) -> &Self::Target { |
| 84 unsafe { &*self.0 } | 19 unsafe { &*self.0 } |
| 85 } | 20 } |
| 86 } | 21 } |
| 87 | 22 |
| 88 impl DerefMut for OwnedLibPamHandle { | 23 impl DerefMut for HandleWrap { |
| 89 fn deref_mut(&mut self) -> &mut Self::Target { | 24 fn deref_mut(&mut self) -> &mut Self::Target { |
| 90 unsafe { &mut *self.0 } | 25 unsafe { &mut *self.0 } |
| 91 } | 26 } |
| 92 } | 27 } |
| 93 | 28 |
| 29 /// An owned PAM handle. | |
| 30 pub struct OwnedLibPamHandle { | |
| 31 handle: HandleWrap, | |
| 32 last_return: Cell<Result<()>>, | |
| 33 } | |
| 34 | |
| 35 // TODO: pam_authenticate - app | |
| 36 // pam_setcred - app | |
| 37 // pam_acct_mgmt - app | |
| 38 // pam_chauthtok - app | |
| 39 // pam_open_session - app | |
| 40 // pam_close_session - app | |
| 41 // pam_putenv - shared | |
| 42 // pam_getenv - shared | |
| 43 // pam_getenvlist - shared | |
| 44 | |
| 94 impl Drop for OwnedLibPamHandle { | 45 impl Drop for OwnedLibPamHandle { |
| 95 /// Ends the PAM session with a zero error code. | 46 /// Closes the PAM session on an owned PAM handle. |
| 96 /// You probably want to call [`close`](Self::close) instead of | 47 /// |
| 97 /// letting this drop by itself. | 48 /// See the [`pam_end` manual page][man] for more information. |
| 49 /// | |
| 50 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_end.3.html | |
| 98 fn drop(&mut self) { | 51 fn drop(&mut self) { |
| 99 unsafe { | 52 unsafe { |
| 100 super::pam_end(self.0, 0); | 53 pam_ffi::pam_end( |
| 54 self.handle.0, | |
| 55 ErrorCode::result_to_c(self.last_return.get()), | |
| 56 ); | |
| 101 } | 57 } |
| 102 } | 58 } |
| 103 } | 59 } |
| 104 | 60 |
| 105 macro_rules! cstr_item { | 61 macro_rules! cstr_item { |
| 106 (get = $getter:ident, item = $item_type:path) => { | 62 (get = $getter:ident, item = $item_type:path) => { |
| 107 fn $getter(&mut self) -> Result<Option<&str>> { | 63 fn $getter(&self) -> Result<Option<&str>> { |
| 108 unsafe { self.get_cstr_item($item_type) } | 64 unsafe { self.get_cstr_item($item_type) } |
| 109 } | 65 } |
| 110 }; | 66 }; |
| 111 (set = $setter:ident, item = $item_type:path) => { | 67 (set = $setter:ident, item = $item_type:path) => { |
| 112 fn $setter(&mut self, value: Option<&str>) -> Result<()> { | 68 fn $setter(&mut self, value: Option<&str>) -> Result<()> { |
| 117 | 73 |
| 118 impl PamShared for LibPamHandle { | 74 impl PamShared for LibPamHandle { |
| 119 fn get_user(&mut self, prompt: Option<&str>) -> Result<&str> { | 75 fn get_user(&mut self, prompt: Option<&str>) -> Result<&str> { |
| 120 let prompt = memory::option_cstr(prompt)?; | 76 let prompt = memory::option_cstr(prompt)?; |
| 121 let mut output: *const c_char = ptr::null(); | 77 let mut output: *const c_char = ptr::null(); |
| 122 let ret = | 78 let ret = unsafe { |
| 123 unsafe { super::pam_get_user(self, &mut output, memory::prompt_ptr(prompt.as_ref())) }; | 79 pam_ffi::pam_get_user(self, &mut output, memory::prompt_ptr(prompt.as_ref())) |
| 80 }; | |
| 124 ErrorCode::result_from(ret)?; | 81 ErrorCode::result_from(ret)?; |
| 125 unsafe { memory::wrap_string(output) } | 82 unsafe { memory::wrap_string(output) } |
| 126 .transpose() | 83 .transpose() |
| 127 .unwrap_or(Err(ErrorCode::ConversationError)) | 84 .unwrap_or(Err(ErrorCode::ConversationError)) |
| 128 } | 85 } |
| 154 } | 111 } |
| 155 } | 112 } |
| 156 } | 113 } |
| 157 } | 114 } |
| 158 | 115 |
| 159 impl PamModuleOnly for LibPamHandle { | 116 impl PamHandleModule for LibPamHandle { |
| 160 fn get_authtok(&mut self, prompt: Option<&str>) -> Result<&str> { | 117 fn get_authtok(&mut self, prompt: Option<&str>) -> Result<&str> { |
| 161 let prompt = memory::option_cstr(prompt)?; | 118 let prompt = memory::option_cstr(prompt)?; |
| 162 let mut output: *const c_char = ptr::null_mut(); | 119 let mut output: *const c_char = ptr::null_mut(); |
| 163 // SAFETY: We're calling this with known-good values. | 120 // SAFETY: We're calling this with known-good values. |
| 164 let res = unsafe { | 121 let res = unsafe { |
| 165 super::pam_get_authtok( | 122 pam_ffi::pam_get_authtok( |
| 166 self, | 123 self, |
| 167 ItemType::AuthTok.into(), | 124 ItemType::AuthTok.into(), |
| 168 &mut output, | 125 &mut output, |
| 169 memory::prompt_ptr(prompt.as_ref()), | 126 memory::prompt_ptr(prompt.as_ref()), |
| 170 ) | 127 ) |
| 188 unsafe { | 145 unsafe { |
| 189 let _data: Box<T> = Box::from_raw(c_data.cast()); | 146 let _data: Box<T> = Box::from_raw(c_data.cast()); |
| 190 } | 147 } |
| 191 } | 148 } |
| 192 | 149 |
| 150 impl LibPamHandle { | |
| 151 /// Gets a C string item. | |
| 152 /// | |
| 153 /// # Safety | |
| 154 /// | |
| 155 /// You better be requesting an item which is a C string. | |
| 156 unsafe fn get_cstr_item(&self, item_type: ItemType) -> Result<Option<&str>> { | |
| 157 let mut output = ptr::null(); | |
| 158 let ret = unsafe { pam_ffi::pam_get_item(self, item_type as c_int, &mut output) }; | |
| 159 ErrorCode::result_from(ret)?; | |
| 160 memory::wrap_string(output.cast()) | |
| 161 } | |
| 162 | |
| 163 /// Sets a C string item. | |
| 164 /// | |
| 165 /// # Safety | |
| 166 /// | |
| 167 /// You better be setting an item which is a C string. | |
| 168 unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&str>) -> Result<()> { | |
| 169 let data_str = memory::option_cstr(data)?; | |
| 170 let ret = unsafe { | |
| 171 pam_ffi::pam_set_item( | |
| 172 self, | |
| 173 item_type as c_int, | |
| 174 memory::prompt_ptr(data_str.as_ref()).cast(), | |
| 175 ) | |
| 176 }; | |
| 177 ErrorCode::result_from(ret) | |
| 178 } | |
| 179 | |
| 180 /// Gets the `PAM_CONV` item from the handle. | |
| 181 fn conversation_item(&mut self) -> Result<&mut LibPamConversation<'_>> { | |
| 182 let output: *mut LibPamConversation = ptr::null_mut(); | |
| 183 let result = unsafe { | |
| 184 pam_ffi::pam_get_item( | |
| 185 self, | |
| 186 ItemType::Conversation.into(), | |
| 187 &mut output.cast_const().cast(), | |
| 188 ) | |
| 189 }; | |
| 190 ErrorCode::result_from(result)?; | |
| 191 // SAFETY: We got this result from PAM, and we're checking if it's null. | |
| 192 unsafe { output.as_mut() }.ok_or(ErrorCode::ConversationError) | |
| 193 } | |
| 194 } | |
| 195 | |
| 196 macro_rules! delegate { | |
| 197 (fn $meth:ident(&self $(, $param:ident: $typ:ty)*) -> Result<$ret:ty>) => { | |
| 198 fn $meth(&self $(, $param: $typ)*) -> Result<$ret> { | |
| 199 let result = self.handle.$meth($($param),*); | |
| 200 self.last_return.set(split(&result)); | |
| 201 result | |
| 202 } | |
| 203 }; | |
| 204 (fn $meth:ident(&mut self $(, $param:ident: $typ:ty)*) -> Result<$ret:ty>) => { | |
| 205 fn $meth(&mut self $(, $param: $typ)*) -> Result<$ret> { | |
| 206 let result = self.handle.$meth($($param),*); | |
| 207 self.last_return.set(split(&result)); | |
| 208 result | |
| 209 } | |
| 210 }; | |
| 211 (get = $get:ident$(, set = $set:ident)?) => { | |
| 212 delegate!(fn $get(&self) -> Result<Option<&str>>); | |
| 213 $(delegate!(set = $set);)? | |
| 214 }; | |
| 215 (set = $set:ident) => { | |
| 216 delegate!(fn $set(&mut self, value: Option<&str>) -> Result<()>); | |
| 217 }; | |
| 218 } | |
| 219 | |
| 220 fn split<T>(result: &Result<T>) -> Result<()> { | |
| 221 result.as_ref().map(drop).map_err(|&e| e) | |
| 222 } | |
| 223 | |
| 224 impl PamShared for OwnedLibPamHandle { | |
| 225 delegate!(fn get_user(&mut self, prompt: Option<&str>) -> Result<&str>); | |
| 226 delegate!(get = user_item, set = set_user_item); | |
| 227 delegate!(get = service, set = set_service); | |
| 228 delegate!(get = user_prompt, set = set_user_prompt); | |
| 229 delegate!(get = tty_name, set = set_tty_name); | |
| 230 delegate!(get = remote_user, set = set_remote_user); | |
| 231 delegate!(get = remote_host, set = set_remote_host); | |
| 232 delegate!(set = set_authtok_item); | |
| 233 delegate!(set = set_old_authtok_item); | |
| 234 } | |
| 235 | |
| 193 /// Identifies what is being gotten or set with `pam_get_item` | 236 /// Identifies what is being gotten or set with `pam_get_item` |
| 194 /// or `pam_set_item`. | 237 /// or `pam_set_item`. |
| 195 #[derive(FromPrimitive)] | 238 #[derive(TryFromPrimitive, IntoPrimitive)] |
| 196 #[repr(i32)] | 239 #[repr(i32)] |
| 197 #[non_exhaustive] // because C could give us anything! | 240 #[non_exhaustive] // because C could give us anything! |
| 198 pub enum ItemType { | 241 pub enum ItemType { |
| 199 /// The PAM service name. | 242 /// The PAM service name. |
| 200 Service = 1, | 243 Service = 1, |
| 221 /// X server authentication data. | 264 /// X server authentication data. |
| 222 XAuthData = 12, | 265 XAuthData = 12, |
| 223 /// The type of `pam_get_authtok`. | 266 /// The type of `pam_get_authtok`. |
| 224 AuthTokType = 13, | 267 AuthTokType = 13, |
| 225 } | 268 } |
| 226 | |
| 227 impl TryFrom<c_int> for ItemType { | |
| 228 type Error = InvalidEnum<Self>; | |
| 229 fn try_from(value: c_int) -> StdResult<Self, Self::Error> { | |
| 230 Self::from_i32(value).ok_or(value.into()) | |
| 231 } | |
| 232 } | |
| 233 | |
| 234 impl From<ItemType> for c_int { | |
| 235 fn from(val: ItemType) -> Self { | |
| 236 val as Self | |
| 237 } | |
| 238 } |
