Mercurial > crates > nonstick
diff 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 |
line wrap: on
line diff
--- a/src/libpam/handle.rs Sun Jun 08 04:21:58 2025 -0400 +++ b/src/libpam/handle.rs Tue Jun 10 01:09:30 2025 -0400 @@ -1,110 +1,66 @@ use super::conversation::LibPamConversation; -use crate::constants::{ErrorCode, InvalidEnum, Result}; +use crate::constants::{ErrorCode, Result}; use crate::conv::Message; -use crate::handle::{PamApplicationOnly, PamModuleOnly, PamShared}; -use crate::libpam::memory; -use crate::libpam::memory::Immovable; -use crate::Conversation; -use num_derive::FromPrimitive; -use num_traits::FromPrimitive; +use crate::handle::PamShared; +pub use crate::libpam::pam_ffi::LibPamHandle; +use crate::libpam::{memory, pam_ffi}; +use crate::{Conversation, PamHandleModule}; +use num_enum::{IntoPrimitive, TryFromPrimitive}; +use std::cell::Cell; use std::ffi::{c_char, c_int}; use std::ops::{Deref, DerefMut}; -use std::result::Result as StdResult; -use std::{mem, ptr}; - -/// An owned PAM handle. -#[repr(transparent)] -pub struct OwnedLibPamHandle(*mut LibPamHandle); - -/// An opaque structure that a PAM handle points to. -#[repr(C)] -pub struct LibPamHandle { - _data: (), - _marker: Immovable, -} - -impl LibPamHandle { - /// Gets a C string item. - /// - /// # Safety - /// - /// You better be requesting an item which is a C string. - unsafe fn get_cstr_item(&mut self, item_type: ItemType) -> Result<Option<&str>> { - let mut output = ptr::null(); - let ret = unsafe { super::pam_get_item(self, item_type as c_int, &mut output) }; - ErrorCode::result_from(ret)?; - memory::wrap_string(output.cast()) - } +use std::ptr; - /// Sets a C string item. - /// - /// # Safety - /// - /// You better be setting an item which is a C string. - unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&str>) -> Result<()> { - let data_str = memory::option_cstr(data)?; - let ret = unsafe { - super::pam_set_item( - self, - item_type as c_int, - memory::prompt_ptr(data_str.as_ref()).cast(), - ) - }; - ErrorCode::result_from(ret) - } +struct HandleWrap(*mut LibPamHandle); - /// Gets the `PAM_CONV` item from the handle. - fn conversation_item(&mut self) -> Result<&mut LibPamConversation<'_>> { - let output: *mut LibPamConversation = ptr::null_mut(); - let result = unsafe { - super::pam_get_item( - self, - ItemType::Conversation.into(), - &mut output.cast_const().cast(), - ) - }; - ErrorCode::result_from(result)?; - // SAFETY: We got this result from PAM, and we're checking if it's null. - unsafe { output.as_mut() }.ok_or(ErrorCode::ConversationError) - } -} - -impl PamApplicationOnly for OwnedLibPamHandle { - fn close(self, status: Result<()>) -> Result<()> { - let ret = unsafe { super::pam_end(self.0, ErrorCode::result_to_c(status)) }; - // Forget rather than dropping, since dropping also calls pam_end. - mem::forget(self); - ErrorCode::result_from(ret) - } -} - -impl Deref for OwnedLibPamHandle { +impl Deref for HandleWrap { type Target = LibPamHandle; fn deref(&self) -> &Self::Target { unsafe { &*self.0 } } } -impl DerefMut for OwnedLibPamHandle { +impl DerefMut for HandleWrap { fn deref_mut(&mut self) -> &mut Self::Target { unsafe { &mut *self.0 } } } +/// An owned PAM handle. +pub struct OwnedLibPamHandle { + handle: HandleWrap, + last_return: Cell<Result<()>>, +} + +// TODO: pam_authenticate - app +// pam_setcred - app +// pam_acct_mgmt - app +// pam_chauthtok - app +// pam_open_session - app +// pam_close_session - app +// pam_putenv - shared +// pam_getenv - shared +// pam_getenvlist - shared + impl Drop for OwnedLibPamHandle { - /// Ends the PAM session with a zero error code. - /// You probably want to call [`close`](Self::close) instead of - /// letting this drop by itself. + /// Closes the PAM session on an owned PAM handle. + /// + /// See the [`pam_end` manual page][man] for more information. + /// + /// [man]: https://www.man7.org/linux/man-pages/man3/pam_end.3.html fn drop(&mut self) { unsafe { - super::pam_end(self.0, 0); + pam_ffi::pam_end( + self.handle.0, + ErrorCode::result_to_c(self.last_return.get()), + ); } } } macro_rules! cstr_item { (get = $getter:ident, item = $item_type:path) => { - fn $getter(&mut self) -> Result<Option<&str>> { + fn $getter(&self) -> Result<Option<&str>> { unsafe { self.get_cstr_item($item_type) } } }; @@ -119,8 +75,9 @@ fn get_user(&mut self, prompt: Option<&str>) -> Result<&str> { let prompt = memory::option_cstr(prompt)?; let mut output: *const c_char = ptr::null(); - let ret = - unsafe { super::pam_get_user(self, &mut output, memory::prompt_ptr(prompt.as_ref())) }; + let ret = unsafe { + pam_ffi::pam_get_user(self, &mut output, memory::prompt_ptr(prompt.as_ref())) + }; ErrorCode::result_from(ret)?; unsafe { memory::wrap_string(output) } .transpose() @@ -156,13 +113,13 @@ } } -impl PamModuleOnly for LibPamHandle { +impl PamHandleModule for LibPamHandle { fn get_authtok(&mut self, prompt: Option<&str>) -> Result<&str> { let prompt = memory::option_cstr(prompt)?; let mut output: *const c_char = ptr::null_mut(); // SAFETY: We're calling this with known-good values. let res = unsafe { - super::pam_get_authtok( + pam_ffi::pam_get_authtok( self, ItemType::AuthTok.into(), &mut output, @@ -190,9 +147,95 @@ } } +impl LibPamHandle { + /// Gets a C string item. + /// + /// # Safety + /// + /// You better be requesting an item which is a C string. + unsafe fn get_cstr_item(&self, item_type: ItemType) -> Result<Option<&str>> { + let mut output = ptr::null(); + let ret = unsafe { pam_ffi::pam_get_item(self, item_type as c_int, &mut output) }; + ErrorCode::result_from(ret)?; + memory::wrap_string(output.cast()) + } + + /// Sets a C string item. + /// + /// # Safety + /// + /// You better be setting an item which is a C string. + unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&str>) -> Result<()> { + let data_str = memory::option_cstr(data)?; + let ret = unsafe { + pam_ffi::pam_set_item( + self, + item_type as c_int, + memory::prompt_ptr(data_str.as_ref()).cast(), + ) + }; + ErrorCode::result_from(ret) + } + + /// Gets the `PAM_CONV` item from the handle. + fn conversation_item(&mut self) -> Result<&mut LibPamConversation<'_>> { + let output: *mut LibPamConversation = ptr::null_mut(); + let result = unsafe { + pam_ffi::pam_get_item( + self, + ItemType::Conversation.into(), + &mut output.cast_const().cast(), + ) + }; + ErrorCode::result_from(result)?; + // SAFETY: We got this result from PAM, and we're checking if it's null. + unsafe { output.as_mut() }.ok_or(ErrorCode::ConversationError) + } +} + +macro_rules! delegate { + (fn $meth:ident(&self $(, $param:ident: $typ:ty)*) -> Result<$ret:ty>) => { + fn $meth(&self $(, $param: $typ)*) -> Result<$ret> { + let result = self.handle.$meth($($param),*); + self.last_return.set(split(&result)); + result + } + }; + (fn $meth:ident(&mut self $(, $param:ident: $typ:ty)*) -> Result<$ret:ty>) => { + fn $meth(&mut self $(, $param: $typ)*) -> Result<$ret> { + let result = self.handle.$meth($($param),*); + self.last_return.set(split(&result)); + result + } + }; + (get = $get:ident$(, set = $set:ident)?) => { + delegate!(fn $get(&self) -> Result<Option<&str>>); + $(delegate!(set = $set);)? + }; + (set = $set:ident) => { + delegate!(fn $set(&mut self, value: Option<&str>) -> Result<()>); + }; +} + +fn split<T>(result: &Result<T>) -> Result<()> { + result.as_ref().map(drop).map_err(|&e| e) +} + +impl PamShared for OwnedLibPamHandle { + delegate!(fn get_user(&mut self, prompt: Option<&str>) -> Result<&str>); + delegate!(get = user_item, set = set_user_item); + delegate!(get = service, set = set_service); + delegate!(get = user_prompt, set = set_user_prompt); + delegate!(get = tty_name, set = set_tty_name); + delegate!(get = remote_user, set = set_remote_user); + delegate!(get = remote_host, set = set_remote_host); + delegate!(set = set_authtok_item); + delegate!(set = set_old_authtok_item); +} + /// Identifies what is being gotten or set with `pam_get_item` /// or `pam_set_item`. -#[derive(FromPrimitive)] +#[derive(TryFromPrimitive, IntoPrimitive)] #[repr(i32)] #[non_exhaustive] // because C could give us anything! pub enum ItemType { @@ -223,16 +266,3 @@ /// The type of `pam_get_authtok`. AuthTokType = 13, } - -impl TryFrom<c_int> for ItemType { - type Error = InvalidEnum<Self>; - fn try_from(value: c_int) -> StdResult<Self, Self::Error> { - Self::from_i32(value).ok_or(value.into()) - } -} - -impl From<ItemType> for c_int { - fn from(val: ItemType) -> Self { - val as Self - } -}