diff src/libpam/handle.rs @ 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 0099f2f79f86
children a75a66cb4181
line wrap: on
line diff
--- 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 {