Mercurial > crates > nonstick
view src/libpam/handle.rs @ 105:13b4d2a19674
Support Rust v1.75.0.
This is the version included in Ubuntu 24.04 LTS and Debian Trixie,
so it's old enough to have wide penetration without being too old
to get new features (Debian Stable, I love you but v1.63 is just
not going to work out).
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Thu, 26 Jun 2025 00:48:51 -0400 |
parents | dfcd96a74ac4 |
children |
line wrap: on
line source
use super::conversation::LibPamConversation; use crate::constants::{ErrorCode, Result}; use crate::conv::Message; use crate::environ::EnvironMapMut; use crate::handle::PamShared; use crate::libpam::environ::{LibPamEnviron, LibPamEnvironMut}; pub use crate::libpam::pam_ffi::LibPamHandle; use crate::libpam::{memory, pam_ffi}; use crate::logging::Level; use crate::{ Conversation, EnvironMap, Flags, PamHandleApplication, PamHandleModule, _guide, _linklist, _stdlinks, }; use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::cell::Cell; use std::ffi::{c_char, c_int, CString}; use std::ops::{Deref, DerefMut}; use std::ptr; /// Owner for a PAM handle. struct HandleWrap(*mut LibPamHandle); impl Deref for HandleWrap { type Target = LibPamHandle; fn deref(&self) -> &Self::Target { unsafe { &*self.0 } } } impl DerefMut for HandleWrap { fn deref_mut(&mut self) -> &mut Self::Target { unsafe { &mut *self.0 } } } /// An owned PAM handle. pub struct OwnedLibPamHandle<'a> { /// The handle itself. handle: HandleWrap, /// The last return value from the handle. last_return: Cell<Result<()>>, /// If set, the Conversation that this PAM handle owns. /// /// We have to hold on to this because the PAM specification doesn't /// actually say what the PAM library should do with a passed-in /// conversation. Linux-PAM copies the contents of the `pam_conv` struct /// that you pass in to `pam_start`, but OpenPAM uses the pointer itself, /// so you have to keep it in one place. conversation: Option<Box<LibPamConversation<'a>>>, } #[derive(Debug, PartialEq)] pub struct HandleBuilder { service_name: String, username: Option<String>, } impl HandleBuilder { /// Updates the service name. pub fn service_name(mut self, service_name: String) -> Self { self.service_name = service_name; self } /// Sets the username. Setting this will avoid the need for an extra /// round trip through the conversation and may otherwise improve /// the login experience. pub fn username(mut self, username: String) -> Self { self.username = Some(username); self } /// Builds a PAM handle and starts the transaction. pub fn build(self, conv: &impl Conversation) -> Result<OwnedLibPamHandle> { OwnedLibPamHandle::start(self.service_name, self.username, conv) } } impl OwnedLibPamHandle<'_> { /// Creates a builder to start a PAM transaction for the given service. /// /// The service name is what controls the steps and checks PAM goes through /// when authenticating a user. This corresponds to the configuration file /// named <code>/etc/pam.d/<var>service_name</var></code>. /// /// # References #[doc = _linklist!(pam_start: adg, _std)] /// #[doc = _stdlinks!(3 pam_start)] #[doc = _guide!(adg: "adg-interface-by-app-expected.html#adg-pam_start")] pub fn build_with_service(service_name: String) -> HandleBuilder { HandleBuilder { service_name, username: None, } } fn start( service_name: String, username: Option<String>, conversation: &impl Conversation, ) -> Result<Self> { let conv = Box::new(LibPamConversation::wrap(conversation)); let service_cstr = CString::new(service_name).map_err(|_| ErrorCode::ConversationError)?; let username_cstr = memory::prompt_ptr(memory::option_cstr(username.as_deref())?.as_ref()); let mut handle: *mut LibPamHandle = ptr::null_mut(); // SAFETY: We've set everything up properly to call `pam_start`. // The returned value will be a valid pointer provided the result is OK. let result = unsafe { pam_ffi::pam_start( service_cstr.as_ptr(), username_cstr, conv.as_ref(), &mut handle, ) }; ErrorCode::result_from(result)?; Ok(Self { handle: HandleWrap(handle), last_return: Cell::new(Ok(())), conversation: Some(conv), }) } } impl PamHandleApplication for OwnedLibPamHandle<'_> { fn authenticate(&mut self, flags: Flags) -> Result<()> { let ret = unsafe { pam_ffi::pam_authenticate(self.handle.0, flags.bits() as c_int) }; let result = ErrorCode::result_from(ret); self.last_return.set(result); result } fn account_management(&mut self, flags: Flags) -> Result<()> { let ret = unsafe { pam_ffi::pam_acct_mgmt(self.handle.0, flags.bits() as c_int) }; let result = ErrorCode::result_from(ret); self.last_return.set(result); result } fn change_authtok(&mut self, flags: Flags) -> Result<()> { let ret = unsafe { pam_ffi::pam_chauthtok(self.handle.0, flags.bits() as c_int) }; let result = ErrorCode::result_from(ret); self.last_return.set(result); 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<'_> { /// Closes the PAM session on an owned PAM handle. /// /// This internally calls `pam_end` with the appropriate error code. /// /// # References #[doc = _linklist!(pam_end: adg, _std)] /// #[doc = _guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")] #[doc = _stdlinks!(3 pam_end)] fn drop(&mut self) { unsafe { pam_ffi::pam_end( self.handle.0, ErrorCode::result_to_c(self.last_return.get()), ); } } } /// Macro to implement getting/setting a CStr-based item. macro_rules! cstr_item { (get = $getter:ident, item = $item_type:path) => { fn $getter(&self) -> Result<Option<String>> { 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 log(&self, level: Level, entry: &str) { let entry = match CString::new(entry).or_else(|_| CString::new(dbg!(entry))) { Ok(cstr) => cstr, _ => return, }; #[cfg(pam_impl = "linux-pam")] { // SAFETY: We're calling this function with a known value. unsafe { pam_ffi::pam_syslog(self, level as c_int, "%s\0".as_ptr().cast(), entry.as_ptr()) } } #[cfg(pam_impl = "openpam")] { // SAFETY: We're calling this function with a known value. unsafe { pam_ffi::openpam_log(self, level as c_int, "%s\0".as_ptr().cast(), entry.as_ptr()) } } } fn username(&mut self, prompt: Option<&str>) -> Result<String> { let prompt = memory::option_cstr(prompt)?; let mut output: *const c_char = ptr::null(); let ret = unsafe { pam_ffi::pam_get_user(self, &mut output, memory::prompt_ptr(prompt.as_ref())) }; ErrorCode::result_from(ret)?; unsafe { memory::copy_pam_string(output) } .transpose() .unwrap_or(Err(ErrorCode::ConversationError)) } fn environ(&self) -> impl EnvironMap { LibPamEnviron::new(self) } fn environ_mut(&mut self) -> impl EnvironMapMut { LibPamEnvironMut::new(self) } 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(&self, messages: &[Message]) { match self.conversation_item() { Ok(conv) => conv.communicate(messages), Err(e) => { for msg in messages { msg.set_error(e) } } } } } impl PamHandleModule for LibPamHandle { fn authtok(&mut self, prompt: Option<&str>) -> Result<String> { 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 { pam_ffi::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::copy_pam_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()); } } 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<String>> { 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::copy_pam_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(&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 { // First have the kind that save the result after delegation. (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 } }; // Then have the kind that are just raw delegates (fn $meth:ident(&self $(, $param:ident: $typ:ty)*) -> $ret:ty) => { fn $meth(&self $(, $param: $typ)*) -> $ret { self.handle.$meth($($param),*) } }; (fn $meth:ident(&mut self $(, $param:ident: $typ:ty)*) -> $ret:ty) => { fn $meth(&mut self $(, $param: $typ)*) -> $ret { self.handle.$meth($($param),*) } }; // Then have item getters / setters (get = $get:ident$(, set = $set:ident)?) => { delegate!(fn $get(&self) -> Result<Option<String>>); $(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 log(&self, level: Level, entry: &str) -> ()); delegate!(fn environ(&self) -> impl EnvironMap); delegate!(fn environ_mut(&mut self) -> impl EnvironMapMut); delegate!(fn username(&mut self, prompt: Option<&str>) -> Result<String>); 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(TryFromPrimitive, IntoPrimitive)] #[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, }