Mercurial > crates > nonstick
changeset 159:634cd5f2ac8b
Separate logging into its own trait apart from the rest of PAM.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sat, 12 Jul 2025 18:16:18 -0400 |
parents | d5b7b28d754e |
children | 09dff285ff5e |
files | src/handle.rs src/libpam/handle.rs src/libpam/memory.rs src/logging.rs testharness/src/lib.rs |
diffstat | 5 files changed, 136 insertions(+), 73 deletions(-) [+] |
line wrap: on
line diff
--- a/src/handle.rs Sat Jul 12 17:17:37 2025 -0400 +++ b/src/handle.rs Sat Jul 12 18:16:18 2025 -0400 @@ -5,9 +5,8 @@ use crate::conv::Conversation; use crate::environ::{EnvironMap, EnvironMapMut}; use crate::items::{getter, Items, ItemsMut}; -use crate::logging::{Level, Location}; +use crate::logging::Logger; use std::ffi::{OsStr, OsString}; -use std::fmt; /// Functionality for both PAM applications and PAM modules. /// @@ -17,41 +16,7 @@ /// You probably want [`LibPamTransaction`](crate::libpam::LibPamTransaction). /// This trait is intended to allow creating mock PAM handle types /// to test PAM modules and applications. -pub trait PamShared { - /// Logs something via this PAM handle. - /// - /// You probably want to use one of the logging macros, - /// like [`error!`](crate::error!), - /// [`warn!`](crate::warn!), - /// [`info!`](crate::info!), - /// or [`debug!`](crate::debug!). - /// - /// In most PAM implementations, this will go to syslog. - /// See [Linux-PAM's `pam_syslog`][man7] or - /// [OpenPAM's `openpam_log`][manbsd] for more details. - /// - /// # Example - /// - /// ```no_run - /// # use nonstick::PamShared; - /// use nonstick::logging::Level; - /// use nonstick::location; - /// # fn _test(pam_hdl: impl PamShared) { - /// # let delay_ms = 100; - /// # let url = "https://zombo.com"; - /// // Usually, instead of calling this manually, just use the macros. - /// nonstick::error!(pam_hdl, "something bad happened!"); - /// nonstick::warn!(pam_hdl, "loading information took {delay_ms} ms"); - /// nonstick::info!(pam_hdl, "using network backend"); - /// nonstick::debug!(pam_hdl, "sending GET request to {url}"); - /// // But if you really want to, you can call this yourself: - /// pam_hdl.log(Level::Warn, location!(), format_args!("this is unnecessarily verbose")); - /// # } - /// ``` - #[doc = man7!(3 pam_syslog)] - #[doc = manbsd!(3 openpam_log)] - fn log(&self, level: Level, loc: Location<'_>, entry: fmt::Arguments); - +pub trait PamShared: Logger { /// Retrieves the name of the user who is authenticating or logging in. /// /// If the username has previously been obtained, this uses that username;
--- a/src/libpam/handle.rs Sat Jul 12 17:17:37 2025 -0400 +++ b/src/libpam/handle.rs Sat Jul 12 18:16:18 2025 -0400 @@ -8,7 +8,7 @@ use crate::libpam::environ::{LibPamEnviron, LibPamEnvironMut}; use crate::libpam::items::{LibPamItems, LibPamItemsMut}; use crate::libpam::{items, memory}; -use crate::logging::{Level, Location}; +use crate::logging::{Level, Location, Logger}; use crate::{Conversation, EnvironMap, Flags, ModuleClient, Transaction}; use libpam_sys_consts::constants; use num_enum::{IntoPrimitive, TryFromPrimitive}; @@ -43,43 +43,45 @@ } impl TransactionBuilder { + /// 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 + /// usually at <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 new_with_service(service_name: impl AsRef<OsStr>) -> Self { + Self { + service_name: service_name.as_ref().into(), + username: None, + } + } + /// Updates the service name. - pub fn service_name(mut self, service_name: OsString) -> Self { - self.service_name = service_name; + pub fn service_name(mut self, service_name: impl AsRef<OsStr>) -> Self { + self.service_name = service_name.as_ref().into(); 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: OsString) -> Self { - self.username = Some(username); + pub fn username(mut self, username: impl AsRef<OsStr>) -> Self { + self.username = Some(username.as_ref().into()); self } - /// Builds a PAM handle and starts the transaction. + + /// Builds the PAM handle and starts the transaction. pub fn build(self, conv: impl Conversation) -> Result<LibPamTransaction<impl Conversation>> { LibPamTransaction::start(self.service_name, self.username, conv) } } impl<C: Conversation> LibPamTransaction<C> { - /// 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: OsString) -> TransactionBuilder { - TransactionBuilder { - service_name, - username: None, - } - } - fn start(service_name: OsString, username: Option<OsString>, conversation: C) -> Result<Self> { let conv = Box::new(OwnedConversation::new(conversation)); let service_cstr = CString::new(service_name.as_bytes()).expect("null is forbidden"); @@ -216,6 +218,10 @@ result.as_ref().map(drop).map_err(|&e| e) } +impl<C: Conversation> Logger for LibPamTransaction<C> { + delegate!(fn log(&self, level: Level, location: Location<'_>, entry: fmt::Arguments) -> ()); +} + impl<C: Conversation> Transaction for LibPamTransaction<C> { delegate!(fn authenticate(&mut self, flags: Flags) -> Result<()>); delegate!(fn account_management(&mut self, flags: Flags) -> Result<()>); @@ -223,7 +229,6 @@ } impl<C: Conversation> PamShared for LibPamTransaction<C> { - delegate!(fn log(&self, level: Level, location: Location<'_>, entry: fmt::Arguments) -> ()); delegate!(fn environ(&self) -> impl EnvironMap); delegate!(fn environ_mut(&mut self) -> impl EnvironMapMut); delegate!(fn username(&mut self, prompt: Option<&OsStr>) -> Result<OsString>); @@ -321,13 +326,13 @@ } } -impl PamShared for LibPamHandle { +impl Logger for LibPamHandle { fn log(&self, level: Level, loc: Location<'_>, entry: fmt::Arguments) { let entry = match CString::new(entry.to_string()).ok() { Some(e) => e, None => return, }; - #[cfg(pam_impl = "LinuxPam")] + #[cfg(any(pam_impl = "LinuxPam", pam_impl = "Sun"))] { let level = match level { Level::Error => libc::LOG_ERR, @@ -337,6 +342,7 @@ }; _ = loc; // SAFETY: We're calling this function with a known value. + #[cfg(pam_impl = "LinuxPam")] unsafe { libpam_sys::pam_syslog( self.raw_ref(), @@ -345,6 +351,10 @@ entry.as_ptr(), ) } + #[cfg(pam_impl = "Sun")] + unsafe { + libpam_sys::__pam_log(level, b"%s\0".as_ptr().cast(), entry.as_ptr()) + } } #[cfg(pam_impl = "OpenPam")] { @@ -366,7 +376,9 @@ } } } +} +impl PamShared for LibPamHandle { fn username(&mut self, prompt: Option<&OsStr>) -> Result<OsString> { let prompt = memory::option_cstr_os(prompt); let mut output: *const c_char = ptr::null(); @@ -488,7 +500,7 @@ #[cfg(any(pam_impl = "LinuxPam", pam_impl = "OpenPam"))] fn get_authtok(&mut self, prompt: Option<&OsStr>, item_type: ItemType) -> Result<OsString> { let prompt = memory::option_cstr_os(prompt); - let mut output: *const c_char = ptr::null_mut(); + let mut output: *const c_char = ptr::null(); // SAFETY: We're calling this with known-good values. let res = unsafe { libpam_sys::pam_get_authtok( @@ -503,9 +515,46 @@ unsafe { memory::copy_pam_string(output) }.ok_or(ErrorCode::ConversationError) } - #[cfg(not(any(pam_impl = "LinuxPam", pam_impl = "OpenPam")))] + #[cfg(pam_impl = "Sun")] fn get_authtok(&mut self, prompt: Option<&OsStr>, item_type: ItemType) -> Result<OsString> { - Err(ErrorCode::ConversationError) + use crate::libpam::memory::CHeapString; + use std::os::unix::ffi::OsStringExt; + // Sun's __pam_get_authtok function is a little weird and requires + // that you specify where you want the authtok to come from. + // First we see if there's an authtok already set. + let mut output: *mut c_char = ptr::null_mut(); + let result = unsafe { + libpam_sys::__pam_get_authtok( + self.raw_mut(), + libpam_sys::PAM_HANDLE, + item_type.into(), + ptr::null(), + &mut output, + ) + }; + let output = unsafe { CHeapString::from_ptr(output) }; + if result == libpam_sys::PAM_SUCCESS { + if let Some(output) = output { + return Ok(OsString::from_vec(output.to_bytes().into())); + } + } + drop(output); + let mut output: *mut c_char = ptr::null_mut(); + let prompt = memory::option_cstr_os(prompt); + let result = unsafe { + libpam_sys::__pam_get_authtok( + self.raw_mut(), + libpam_sys::PAM_PROMPT, + item_type.into(), + memory::prompt_ptr(prompt.as_deref()), + &mut output, + ) + }; + let output = unsafe { CHeapString::from_ptr(output) }; + ErrorCode::result_from(result)?; + output + .map(|s| OsString::from_vec(s.to_bytes().into())) + .ok_or(ErrorCode::ConversationError) } /// Gets the `PAM_CONV` item from the handle. @@ -526,7 +575,7 @@ /// Identifies what is being gotten or set with `pam_get_item` /// or `pam_set_item`. -#[derive(TryFromPrimitive, IntoPrimitive)] +#[derive(Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[repr(i32)] #[non_exhaustive] // because C could give us anything! pub enum ItemType {
--- a/src/libpam/memory.rs Sat Jul 12 17:17:37 2025 -0400 +++ b/src/libpam/memory.rs Sat Jul 12 18:16:18 2025 -0400 @@ -202,11 +202,16 @@ /// /// You have to provide a pointer to the start of an allocation that is /// a valid 0-terminated C string. - unsafe fn from_ptr(ptr: *mut c_char) -> Option<Self> { + pub unsafe fn from_ptr(ptr: *mut c_char) -> Option<Self> { NonNull::new(ptr).map(|p| unsafe { Self(CHeapBox::from_ptr(p)) }) } - unsafe fn from_box<T>(bx: CHeapBox<T>) -> Self { + /// Takes ownership of a CHeapBox. + /// + /// # Safety + /// + /// The box has to point to a valid 0-terminated C string. + pub unsafe fn from_box<T>(bx: CHeapBox<T>) -> Self { Self(CHeapBox::cast(bx)) }
--- a/src/logging.rs Sat Jul 12 17:17:37 2025 -0400 +++ b/src/logging.rs Sat Jul 12 18:16:18 2025 -0400 @@ -15,6 +15,46 @@ //! and may even itself implement `log::Log`, but that interface is not exposed //! to the generic PAM user. +use crate::_doc::{man7, manbsd}; +use std::fmt; + +/// A trait for logging. +pub trait Logger { + /// Logs something via this PAM handle. + /// + /// You probably want to use one of the logging macros, + /// like [`error!`](crate::error!), + /// [`warn!`](crate::warn!), + /// [`info!`](crate::info!), + /// or [`debug!`](crate::debug!). + /// + /// In most PAM implementations, this will go to syslog. + /// See [Linux-PAM's `pam_syslog`][man7] or + /// [OpenPAM's `openpam_log`][manbsd] for more details. + /// + /// # Example + /// + /// ```no_run + /// # use nonstick::PamShared; + /// use nonstick::logging::Level; + /// use nonstick::location; + /// # fn _test(pam_hdl: impl PamShared) { + /// # let delay_ms = 100; + /// # let url = "https://zombo.com"; + /// // Usually, instead of calling this manually, just use the macros. + /// nonstick::error!(pam_hdl, "something bad happened!"); + /// nonstick::warn!(pam_hdl, "loading information took {delay_ms} ms"); + /// nonstick::info!(pam_hdl, "using network backend"); + /// nonstick::debug!(pam_hdl, "sending GET request to {url}"); + /// // But if you really want to, you can call this yourself: + /// pam_hdl.log(Level::Warn, location!(), format_args!("this is unnecessarily verbose")); + /// # } + /// ``` + #[doc = man7!(3 pam_syslog)] + #[doc = manbsd!(3 openpam_log)] + fn log(&self, level: Level, loc: Location<'_>, entry: fmt::Arguments); +} + /// An entry to be added to the log. /// /// The levels are in descending order of importance and correspond roughly @@ -167,15 +207,15 @@ #[test] fn test_logging() { - struct Logger(RefCell<Vec<(Level, String)>>); + struct TestLog(RefCell<Vec<(Level, String)>>); - impl Logger { + impl Logger for TestLog { fn log(&self, level: Level, _: Location<'_>, text: fmt::Arguments) { self.0.borrow_mut().push((level, text.to_string())) } } - let logger = Logger(Default::default()); + let logger = TestLog(Default::default()); let something = Level::Error; error!(logger, "here is another thing: {}", 99);
--- a/testharness/src/lib.rs Sat Jul 12 17:17:37 2025 -0400 +++ b/testharness/src/lib.rs Sat Jul 12 18:16:18 2025 -0400 @@ -18,6 +18,10 @@ ) -> nonstick::Result<()> { Ok(()) } + + fn change_authtok(_handle: &mut M, _args: Vec<&CStr>, _flags: Flags) -> nonstick::Result<()> { + todo!() + } } pam_hooks!(TestHarness);