diff src/libpam/handle.rs @ 97:efe2f5f8b5b2

Implement "stateless" application-side PAM calls. This introduces `authenticate`, `account_management`, and `change_authtok`. These are the three PAM operations that are stateless (i.e., they don't start a session or modify global credentials).
author Paul Fisher <paul@pfish.zone>
date Mon, 23 Jun 2025 19:10:34 -0400
parents f3e260f9ddcb
children b87100c5eed4
line wrap: on
line diff
--- a/src/libpam/handle.rs	Mon Jun 23 14:26:34 2025 -0400
+++ b/src/libpam/handle.rs	Mon Jun 23 19:10:34 2025 -0400
@@ -5,7 +5,7 @@
 pub use crate::libpam::pam_ffi::LibPamHandle;
 use crate::libpam::{memory, pam_ffi};
 use crate::logging::Level;
-use crate::{Conversation, PamHandleModule};
+use crate::{Conversation, Flags, PamHandleApplication, PamHandleModule};
 use num_enum::{IntoPrimitive, TryFromPrimitive};
 use std::cell::Cell;
 use std::ffi::{c_char, c_int, CString};
@@ -36,6 +36,76 @@
     _conversation_lifetime: PhantomData<&'a mut ()>,
 }
 
+#[derive(Debug, PartialEq)]
+pub struct HandleBuilder {
+    service_name: String,
+    username: Option<String>,
+}
+
+impl HandleBuilder {
+    /// Creates a new HandleBuilder for the given service.
+    fn new(service_name: String) -> Self {
+        Self{service_name, username: Default::default()}
+    }
+    /// Updates the service name.
+    pub fn service_name(mut self, service_name: String) -> Self {
+        self.service_name = service_name; self
+    }
+    /// Updates the username.
+    pub fn username(mut self, username: String) -> Self {
+        self.username = Some(username); self
+    }
+
+    pub fn build(self, conv: &impl Conversation) -> Result<OwnedLibPamHandle> {
+        OwnedLibPamHandle::start(self.service_name, self.username, conv)
+    }
+}
+
+impl OwnedLibPamHandle<'_> {
+    pub fn build_with_service(service_name: String) -> HandleBuilder {
+        HandleBuilder::new(service_name)
+    }
+    fn start(service_name: String, username: Option<String>, conversation: &impl Conversation) -> Result<Self> {
+        let conv = 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, &mut handle) };
+        ErrorCode::result_from(result)?;
+        Ok(Self{
+            handle: HandleWrap(handle),
+            last_return: Cell::new(Ok(())),
+            _conversation_lifetime: Default::default(),
+        })
+    }
+}
+
+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