diff src/module.rs @ 56:daa2cde64601

Big big refactor. Probably should have been multiple changes. - Makes FFI safer by explicitly specifying c_int in calls. - Uses ToPrimitive/FromPrimitive to make this easier. - Pulls PamFlag variables into a bitflags! struct. - Pulls PamMessageStyle variables into an enum. - Renames ResultCode to ErrorCode. - Switches from PAM_SUCCESS to using a Result<(), ErrorCode>. - Uses thiserror to make ErrorCode into an Error. - Gets rid of pam_try! because now we have Results. - Expands some names (e.g. Conv to Conversation). - Adds more doc comments. - Returns passwords as a SecureString, to avoid unnecessarily keeping it around in memory.
author Paul Fisher <paul@pfish.zone>
date Sun, 04 May 2025 02:56:55 -0400
parents 9d1160b02d2c
children 3f4a77aa88be
line wrap: on
line diff
--- a/src/module.rs	Sun May 04 00:58:04 2025 -0400
+++ b/src/module.rs	Sun May 04 02:56:55 2025 -0400
@@ -1,9 +1,10 @@
 //! Functions for use in pam modules.
 
-use crate::constants::{PamFlag, PamResultCode};
+use crate::constants::{Flags, PamResult, ErrorCode};
 use crate::items::{Item, ItemType};
 use libc::c_char;
-use std::ffi::{CStr, CString};
+use std::ffi::{c_int, CStr, CString};
+use secure_string::SecureString;
 
 /// Opaque type, used as a pointer when making pam API calls.
 ///
@@ -21,7 +22,7 @@
         pamh: *const PamHandle,
         module_data_name: *const c_char,
         data: &mut *const libc::c_void,
-    ) -> PamResultCode;
+    ) -> c_int;
 
     fn pam_set_data(
         pamh: *const PamHandle,
@@ -30,45 +31,39 @@
         cleanup: extern "C" fn(
             pamh: *const PamHandle,
             data: *mut libc::c_void,
-            error_status: PamResultCode,
+            error_status: c_int,
         ),
-    ) -> PamResultCode;
+    ) -> c_int;
 
     fn pam_get_item(
         pamh: *const PamHandle,
-        item_type: ItemType,
+        item_type: c_int,
         item: &mut *const libc::c_void,
-    ) -> PamResultCode;
+    ) -> c_int;
 
-    fn pam_set_item(
-        pamh: *mut PamHandle,
-        item_type: ItemType,
-        item: *const libc::c_void,
-    ) -> PamResultCode;
+    fn pam_set_item(pamh: *mut PamHandle, item_type: c_int, item: *const libc::c_void) -> c_int;
 
-    fn pam_get_user(
-        pamh: *const PamHandle,
-        user: &*mut c_char,
-        prompt: *const c_char,
-    ) -> PamResultCode;
+    fn pam_get_user(pamh: *const PamHandle, user: &*mut c_char, prompt: *const c_char) -> c_int;
 
     fn pam_get_authtok(
         pamh: *const PamHandle,
-        item_type: ItemType,
+        item_type: c_int,
         data: &*mut c_char,
         prompt: *const c_char,
-    ) -> PamResultCode;
+    ) -> c_int;
 
 }
 
-pub extern "C" fn cleanup<T>(_: *const PamHandle, c_data: *mut libc::c_void, _: PamResultCode) {
+/// 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 cleanup<T>(_: *const PamHandle, c_data: *mut libc::c_void, _: c_int) {
     unsafe {
         let _data: Box<T> = Box::from_raw(c_data.cast::<T>());
     }
 }
 
-pub type PamResult<T> = Result<T, PamResultCode>;
-
 impl PamHandle {
     /// Gets some value, identified by `key`, that has been set by the module
     /// previously.
@@ -87,9 +82,9 @@
     ///
     /// The data, if present, is owned by the current PAM conversation.
     pub unsafe fn get_data<T>(&self, key: &str) -> PamResult<Option<&T>> {
-        let c_key = CString::new(key).map_err(|_| PamResultCode::PAM_CONV_ERR)?;
+        let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?;
         let mut ptr: *const libc::c_void = std::ptr::null();
-        to_result(pam_get_data(self, c_key.as_ptr(), &mut ptr))?;
+        ErrorCode::result_from(pam_get_data(self, c_key.as_ptr(), &mut ptr))?;
         match ptr.is_null() {
             true => Ok(None),
             false => {
@@ -109,8 +104,8 @@
     ///
     /// Returns an error if the underlying PAM function call fails.
     pub fn set_data<T>(&mut self, key: &str, data: Box<T>) -> PamResult<()> {
-        let c_key = CString::new(key).map_err(|_| PamResultCode::PAM_CONV_ERR)?;
-        let res = unsafe {
+        let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?;
+        let ret = unsafe {
             pam_set_data(
                 self,
                 c_key.as_ptr(),
@@ -118,7 +113,7 @@
                 cleanup::<T>,
             )
         };
-        to_result(res)
+        ErrorCode::result_from(ret)
     }
 
     /// Retrieves a value that has been set, possibly by the pam client.
@@ -136,8 +131,8 @@
     pub fn get_item<T: crate::items::Item>(&self) -> PamResult<Option<T>> {
         let mut ptr: *const libc::c_void = std::ptr::null();
         let out = unsafe {
-            let r = pam_get_item(self, T::type_id(), &mut ptr);
-            to_result(r)?;
+            let ret = pam_get_item(self, T::type_id().into(), &mut ptr);
+            ErrorCode::result_from(ret)?;
             let typed_ptr = ptr.cast::<T::Raw>();
             match typed_ptr.is_null() {
                 true => None,
@@ -156,9 +151,9 @@
     ///
     /// Returns an error if the underlying PAM function call fails.
     pub fn set_item<T: Item>(&mut self, item: T) -> PamResult<()> {
-        let res =
-            unsafe { pam_set_item(self, T::type_id(), item.into_raw().cast::<libc::c_void>()) };
-        to_result(res)
+        let ret =
+            unsafe { pam_set_item(self, T::type_id().into(), item.into_raw().cast::<libc::c_void>()) };
+        ErrorCode::result_from(ret)
     }
 
     /// Retrieves the name of the user who is authenticating or logging in.
@@ -174,11 +169,9 @@
     pub fn get_user(&self, prompt: Option<&str>) -> PamResult<String> {
         let prompt = option_cstr(prompt)?;
         let output: *mut c_char = std::ptr::null_mut();
-        let res = unsafe { pam_get_user(self, &output, prompt_ptr(prompt.as_ref())) };
-        match res {
-            PamResultCode::PAM_SUCCESS => copy_pam_string(output),
-            otherwise => Err(otherwise),
-        }
+        let ret = unsafe { pam_get_user(self, &output, prompt_ptr(prompt.as_ref())) };
+        ErrorCode::result_from(ret)?;
+        copy_pam_string(output)
     }
 
     /// Retrieves the authentication token from the user.
@@ -191,19 +184,19 @@
     /// # Errors
     ///
     /// Returns an error if the underlying PAM function call fails.
-    pub fn get_authtok(&self, prompt: Option<&str>) -> PamResult<String> {
+    pub fn get_authtok(&self, prompt: Option<&str>) -> PamResult<SecureString> {
         let prompt = option_cstr(prompt)?;
         let output: *mut c_char = std::ptr::null_mut();
         let res = unsafe {
             pam_get_authtok(
                 self,
-                ItemType::AuthTok,
+                ItemType::AuthTok.into(),
                 &output,
                 prompt_ptr(prompt.as_ref()),
             )
         };
-        to_result(res)?;
-        copy_pam_string(output)
+        ErrorCode::result_from(res)?;
+        copy_pam_string(output).map(SecureString::from)
     }
 }
 
@@ -212,11 +205,11 @@
     prompt
         .map(CString::new)
         .transpose()
-        .map_err(|_| PamResultCode::PAM_CONV_ERR)
+        .map_err(|_| ErrorCode::ConversationError)
 }
 
 /// The pointer to the prompt CString, or null if absent.
-fn prompt_ptr(prompt: Option<&CString>) -> *const c_char {
+pub(crate) fn prompt_ptr(prompt: Option<&CString>) -> *const c_char {
     match prompt {
         Some(c_str) => c_str.as_ptr(),
         None => std::ptr::null(),
@@ -225,24 +218,16 @@
 
 /// Creates an owned copy of a string that is returned from a
 /// <code>pam_get_<var>whatever</var></code> function.
-fn copy_pam_string(result_ptr: *const c_char) -> PamResult<String> {
+pub(crate) fn copy_pam_string(result_ptr: *const c_char) -> PamResult<String> {
     // We really shouldn't get a null pointer back here, but if we do, return nothing.
     if result_ptr.is_null() {
         return Ok(String::new());
     }
     let bytes = unsafe { CStr::from_ptr(result_ptr) };
-    Ok(bytes
+    bytes
         .to_str()
-        .map_err(|_| PamResultCode::PAM_CONV_ERR)?
-        .into())
-}
-
-/// Convenience to transform a `PamResultCode` into a unit `PamResult`.
-fn to_result(result: PamResultCode) -> PamResult<()> {
-    match result {
-        PamResultCode::PAM_SUCCESS => Ok(()),
-        otherwise => Err(otherwise),
-    }
+        .map(String::from)
+        .map_err(|_| ErrorCode::ConversationError)
 }
 
 /// Provides functions that are invoked by the entrypoints generated by the
@@ -258,13 +243,13 @@
     /// authentication module. This function checks for other things. Such things might be: the time of
     /// day or the date, the terminal line, remote hostname, etc. This function may also determine
     /// things like the expiration on passwords, and respond that the user change it before continuing.
-    fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
-        PamResultCode::PAM_IGNORE
+    fn acct_mgmt(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
+        Err(ErrorCode::Ignore)
     }
 
     /// This function performs the task of authenticating the user.
-    fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
-        PamResultCode::PAM_IGNORE
+    fn sm_authenticate(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
+        Err(ErrorCode::Ignore)
     }
 
     /// This function is used to (re-)set the authentication token of the user.
@@ -273,18 +258,18 @@
     /// `PAM_PRELIM_CHECK` and then, if the module does not return `PAM_TRY_AGAIN`, subsequently with
     /// `PAM_UPDATE_AUTHTOK`. It is only on the second call that the authorization token is
     /// (possibly) changed.
-    fn sm_chauthtok(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
-        PamResultCode::PAM_IGNORE
+    fn sm_chauthtok(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
+        Err(ErrorCode::Ignore)
     }
 
     /// This function is called to terminate a session.
-    fn sm_close_session(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
-        PamResultCode::PAM_IGNORE
+    fn sm_close_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
+        Err(ErrorCode::Ignore)
     }
 
     /// This function is called to commence a session.
-    fn sm_open_session(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
-        PamResultCode::PAM_IGNORE
+    fn sm_open_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
+        Err(ErrorCode::Ignore)
     }
 
     /// This function performs the task of altering the credentials of the user with respect to the
@@ -292,7 +277,7 @@
     /// information about a user than their authentication token. This function is used to make such
     /// information available to the application. It should only be called after the user has been
     /// authenticated but before a session has been established.
-    fn sm_setcred(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
-        PamResultCode::PAM_IGNORE
+    fn sm_setcred(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
+        Err(ErrorCode::Ignore)
     }
 }