Mercurial > crates > nonstick
diff src/libpam/handle.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/handle.rs@c7c596e6388f |
children | 351bdc13005e |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/libpam/handle.rs Fri Jun 06 22:35:08 2025 -0400 @@ -0,0 +1,231 @@ +use super::conversation::LibPamConversation; +use crate::constants::{ErrorCode, InvalidEnum, Result}; +use crate::conv::Message; +use crate::handle::{PamApplicationOnly, PamModuleOnly, PamShared}; +use crate::libpam::memory; +use crate::libpam::memory::Immovable; +use crate::{Conversation, Response}; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +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()) + } + + /// 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) + } + + /// 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 { + type Target = LibPamHandle; + fn deref(&self) -> &Self::Target { + unsafe { &*self.0 } + } +} + +impl DerefMut for OwnedLibPamHandle { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut *self.0 } + } +} + +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. + fn drop(&mut self) { + unsafe { + super::pam_end(self.0, 0); + } + } +} + +macro_rules! cstr_item { + (get = $getter:ident, item = $item_type:path) => { + fn $getter(&mut self) -> Result<Option<&str>> { + unsafe { self.get_cstr_item($item_type) } + } + }; + (set = $setter:ident, item = $item_type:path) => { + fn $setter(&mut self, value: Option<&str>) -> Result<()> { + unsafe { self.set_cstr_item($item_type, value) } + } + }; +} + +impl PamShared for LibPamHandle { + 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())) }; + ErrorCode::result_from(ret)?; + unsafe { memory::wrap_string(output) } + .transpose() + .unwrap_or(Err(ErrorCode::ConversationError)) + } + + cstr_item!(get = user_item, item = ItemType::User); + cstr_item!(set = set_user_item, item = ItemType::User); + cstr_item!(get = service, item = ItemType::Service); + cstr_item!(set = set_service, item = ItemType::Service); + cstr_item!(get = user_prompt, item = ItemType::UserPrompt); + cstr_item!(set = set_user_prompt, item = ItemType::UserPrompt); + cstr_item!(get = tty_name, item = ItemType::Tty); + cstr_item!(set = set_tty_name, item = ItemType::Tty); + cstr_item!(get = remote_user, item = ItemType::RemoteUser); + cstr_item!(set = set_remote_user, item = ItemType::RemoteUser); + cstr_item!(get = remote_host, item = ItemType::RemoteHost); + cstr_item!(set = set_remote_host, item = ItemType::RemoteHost); + cstr_item!(set = set_authtok_item, item = ItemType::AuthTok); + cstr_item!(set = set_old_authtok_item, item = ItemType::OldAuthTok); +} + +impl Conversation for LibPamHandle { + fn communicate(&mut self, messages: &[Message]) -> Result<Vec<Response>> { + self.conversation_item()?.communicate(messages) + } +} + +impl PamModuleOnly 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( + self, + ItemType::AuthTok.into(), + &mut output, + memory::prompt_ptr(prompt.as_ref()), + ) + }; + ErrorCode::result_from(res)?; + // SAFETY: We got this string from PAM. + unsafe { memory::wrap_string(output) } + .transpose() + .unwrap_or(Err(ErrorCode::ConversationError)) + } + + cstr_item!(get = authtok_item, item = ItemType::AuthTok); + cstr_item!(get = old_authtok_item, item = ItemType::OldAuthTok); +} + +/// Function called at the end of a PAM session that is called to clean up +/// a value previously provided to PAM in a `pam_set_data` call. +/// +/// You should never call this yourself. +extern "C" fn set_data_cleanup<T>(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) { + unsafe { + let _data: Box<T> = Box::from_raw(c_data.cast()); + } +} + +/// Identifies what is being gotten or set with `pam_get_item` +/// or `pam_set_item`. +#[derive(FromPrimitive)] +#[repr(i32)] +#[non_exhaustive] // because C could give us anything! +pub enum ItemType { + /// The PAM service name. + Service = 1, + /// The user's login name. + User = 2, + /// The TTY name. + Tty = 3, + /// The remote host (if applicable). + RemoteHost = 4, + /// The conversation struct (not a CStr-based item). + Conversation = 5, + /// The authentication token (password). + AuthTok = 6, + /// The old authentication token (when changing passwords). + OldAuthTok = 7, + /// The remote user's name. + RemoteUser = 8, + /// The prompt shown when requesting a username. + UserPrompt = 9, + /// App-supplied function to override failure delays. + FailDelay = 10, + /// X display name. + XDisplay = 11, + /// X server authentication data. + XAuthData = 12, + /// 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 + } +}