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);