changeset 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 676675c3d434
children 2a5c83d04b93
files Cargo.toml src/constants.rs src/conv.rs src/items.rs src/macros.rs src/module.rs
diffstat 6 files changed, 231 insertions(+), 200 deletions(-) [+]
line wrap: on
line diff
--- a/Cargo.toml	Sun May 04 00:58:04 2025 -0400
+++ b/Cargo.toml	Sun May 04 02:56:55 2025 -0400
@@ -10,5 +10,9 @@
 edition = "2021"
 
 [dependencies]
+bitflags = "2.9.0"
 libc = "0.2.97"
+num-derive = "0.4.2"
+num-traits = "0.2.19"
+secure-string = "0.3.0"
 thiserror = "2.0.12"
--- a/src/constants.rs	Sun May 04 00:58:04 2025 -0400
+++ b/src/constants.rs	Sun May 04 02:56:55 2025 -0400
@@ -1,99 +1,144 @@
+use bitflags::bitflags;
 use libc::{c_int, c_uint};
-
+use num_derive::{FromPrimitive, ToPrimitive};
+use num_traits::{FromPrimitive, ToPrimitive};
 // TODO: Import constants from C header file at compile time.
 
-pub type PamFlag = c_uint;
-pub type PamItemType = c_int;
-pub type PamMessageStyle = c_int;
-
 // The Linux-PAM flags
 // see /usr/include/security/_pam_types.h
-pub const PAM_SILENT: PamFlag = 0x8000;
-pub const PAM_DISALLOW_NULL_AUTHTOK: PamFlag = 0x0001;
-pub const PAM_ESTABLISH_CRED: PamFlag = 0x0002;
-pub const PAM_DELETE_CRED: PamFlag = 0x0004;
-pub const PAM_REINITIALIZE_CRED: PamFlag = 0x0008;
-pub const PAM_REFRESH_CRED: PamFlag = 0x0010;
-pub const PAM_CHANGE_EXPIRED_AUTHTOK: PamFlag = 0x0020;
+bitflags! {
+    #[derive(Debug, PartialEq)]
+    #[repr(transparent)]
+    pub struct Flags: c_uint {
+        const SILENT = 0x8000;
+        const DISALLOW_NULL_AUTHTOK = 0x0001;
+        const ESTABLISH_CRED = 0x0002;
+        const DELETE_CRED = 0x0004;
+        const REINITIALIZE_CRED = 0x0008;
+        const REFRESH_CRED = 0x0010;
+        const CHANGE_EXPIRED_AUTHTOK= 0x0020;
+    }
+}
 
-// Message styles
-pub const PAM_PROMPT_ECHO_OFF: PamMessageStyle = 1;
-pub const PAM_PROMPT_ECHO_ON: PamMessageStyle = 2;
-pub const PAM_ERROR_MSG: PamMessageStyle = 3;
-pub const PAM_TEXT_INFO: PamMessageStyle = 4;
-/// yes/no/maybe conditionals
-pub const PAM_RADIO_TYPE: PamMessageStyle = 5;
-pub const PAM_BINARY_PROMPT: PamMessageStyle = 7;
+/// Styles of message that are shown to the user.
+#[derive(Debug, PartialEq, FromPrimitive, ToPrimitive)]
+#[non_exhaustive] // non-exhaustive because C might give us back anything!
+pub enum MessageStyle {
+    /// Requests information from the user; will be masked when typing.
+    PromptEchoOff = 1,
+    /// Requests information from the user; will not be masked.
+    PromptEchoOn = 2,
+    /// An error message.
+    ErrorMsg = 3,
+    /// An informational message.
+    TextInfo = 4,
+    /// Yes/No/Maybe conditionals. Linux-PAM specific.
+    RadioType = 5,
+    /// For server–client non-human interaction.
+    /// NOT part of the X/Open PAM specification.
+    BinaryPrompt = 7,
+}
 
-/// The Linux-PAM return values.
+impl From<MessageStyle> for c_int {
+    fn from(val: MessageStyle) -> Self {
+        val.to_i32().unwrap_or(0)
+    }
+}
+
+/// The Linux-PAM error return values.
+/// Success is instead represented by the `Ok` entry of a `Result`.
+/// Most abbreviations (except `AuthTok` and `Max`) are now full words.
 /// For more detailed information, see
-/// /usr/include/security/_pam_types.h
+/// `/usr/include/security/_pam_types.h`.
 #[allow(non_camel_case_types, dead_code)]
-#[derive(Debug, PartialEq, thiserror::Error)]
-#[repr(C)]
-pub enum PamResultCode {
-    #[error("Not an error")]
-    PAM_SUCCESS = 0,
+#[derive(Copy, Clone, Debug, PartialEq, thiserror::Error, FromPrimitive, ToPrimitive)]
+#[non_exhaustive] // C might give us anything!
+pub enum ErrorCode {
     #[error("dlopen() failure when dynamically loading a service module")]
-    PAM_OPEN_ERR = 1,
+    OpenError = 1,
     #[error("symbol not found")]
-    PAM_SYMBOL_ERR = 2,
+    SymbolError = 2,
     #[error("error in service module")]
-    PAM_SERVICE_ERR = 3,
+    ServiceError = 3,
     #[error("system error")]
-    PAM_SYSTEM_ERR = 4,
+    SystemError = 4,
     #[error("memory buffer error")]
-    PAM_BUF_ERR = 5,
+    BufferError = 5,
     #[error("permission denied")]
-    PAM_PERM_DENIED = 6,
+    PermissionDenied = 6,
     #[error("authentication failure")]
-    PAM_AUTH_ERR = 7,
+    AuthenticationError = 7,
     #[error("cannot access authentication data due to insufficient credentials")]
-    PAM_CRED_INSUFFICIENT = 8,
+    CredentialsInsufficient = 8,
     #[error("underlying authentication service cannot retrieve authentication information")]
-    PAM_AUTHINFO_UNAVAIL = 9,
+    AuthInfoUnavailable = 9,
     #[error("user not known to the underlying authentication module")]
-    PAM_USER_UNKNOWN = 10,
+    UserUnknown = 10,
     #[error("retry limit reached; do not attempt further")]
-    PAM_MAXTRIES = 11,
+    MaxTries = 11,
     #[error("new authentication token required")]
-    PAM_NEW_AUTHTOK_REQD = 12,
+    NewAuthTokRequired = 12,
     #[error("user account has expired")]
-    PAM_ACCT_EXPIRED = 13,
+    AccountExpired = 13,
     #[error("cannot make/remove an entry for the specified session")]
-    PAM_SESSION_ERR = 14,
+    SessionError = 14,
     #[error("underlying authentication service cannot retrieve user credentials")]
-    PAM_CRED_UNAVAIL = 15,
+    CredentialsUnavailable = 15,
     #[error("user credentials expired")]
-    PAM_CRED_EXPIRED = 16,
+    CredentialsExpired = 16,
     #[error("failure setting user credentials")]
-    PAM_CRED_ERR = 17,
+    CredentialsError = 17,
     #[error("no module-specific data is present")]
-    PAM_NO_MODULE_DATA = 18,
+    NoModuleData = 18,
     #[error("conversation error")]
-    PAM_CONV_ERR = 19,
+    ConversationError = 19,
     #[error("authentication token manipulation error")]
-    PAM_AUTHTOK_ERR = 20,
+    AuthTokError = 20,
     #[error("authentication information cannot be recovered")]
-    PAM_AUTHTOK_RECOVERY_ERR = 21,
+    AuthTokRecoveryError = 21,
     #[error("authentication token lock busy")]
-    PAM_AUTHTOK_LOCK_BUSY = 22,
+    AuthTokLockBusy = 22,
     #[error("authentication token aging disabled")]
-    PAM_AUTHTOK_DISABLE_AGING = 23,
+    AuthTokDisableAging = 23,
     #[error("preliminary check by password service")]
-    PAM_TRY_AGAIN = 24,
+    TryAgain = 24,
     #[error("ignore underlying account module, regardless of control flag")]
-    PAM_IGNORE = 25,
+    Ignore = 25,
     #[error("critical error; this module should fail now")]
-    PAM_ABORT = 26,
+    Abort = 26,
     #[error("authentication token has expired")]
-    PAM_AUTHTOK_EXPIRED = 27,
+    AuthTokExpired = 27,
     #[error("module is not known")]
-    PAM_MODULE_UNKNOWN = 28,
+    ModuleUnknown = 28,
     #[error("bad item passed to pam_[whatever]_item")]
-    PAM_BAD_ITEM = 29,
+    BadItem = 29,
     #[error("conversation function is event-driven and data is not available yet")]
-    PAM_CONV_AGAIN = 30,
+    ConversationAgain = 30,
     #[error("call this function again to complete authentication stack")]
-    PAM_INCOMPLETE = 31,
+    Incomplete = 31,
 }
+
+pub type PamResult<T> = Result<T, ErrorCode>;
+impl ErrorCode {
+    /// Converts a PamResult into the result code that C wants.
+    pub fn result_to_c(value: PamResult<()>) -> c_int {
+        match value {
+            Ok(_) => 0, // PAM_SUCCESS
+            Err(otherwise) => otherwise.into(),
+        }
+    }
+
+    /// Converts a C result code into a PamResult, with success as Ok.
+    pub fn result_from(value: c_int) -> PamResult<()> {
+        match value {
+            0 => Ok(()),
+            value => Err(Self::from_i64(value as i64).unwrap_or(Self::ConversationError))
+        }
+    }
+}
+
+impl From<ErrorCode> for c_int {
+    fn from(val: ErrorCode) -> Self {
+        val.to_i32().unwrap_or(0)
+    }
+}
--- a/src/conv.rs	Sun May 04 00:58:04 2025 -0400
+++ b/src/conv.rs	Sun May 04 02:56:55 2025 -0400
@@ -2,19 +2,19 @@
 use std::ffi::{CStr, CString};
 use std::ptr;
 
-use crate::constants::PamMessageStyle;
-use crate::constants::PamResultCode;
+use crate::constants::MessageStyle;
+use crate::constants::PamResult;
+use crate::constants::ErrorCode;
 use crate::items::Item;
-use crate::module::PamResult;
 
 #[repr(C)]
-struct PamMessage {
-    msg_style: PamMessageStyle,
+struct Message {
+    msg_style: MessageStyle,
     msg: *const c_char,
 }
 
 #[repr(C)]
-struct PamResponse {
+struct Response {
     resp: *const c_char,
     resp_retcode: libc::c_int, // Unused - always zero
 }
@@ -23,10 +23,10 @@
 pub struct Inner {
     conv: extern "C" fn(
         num_msg: c_int,
-        pam_message: &&PamMessage,
-        pam_response: &mut *const PamResponse,
+        pam_message: &&Message,
+        pam_response: &mut *const Response,
         appdata_ptr: *const libc::c_void,
-    ) -> PamResultCode,
+    ) -> c_int,
     appdata_ptr: *const libc::c_void,
 }
 
@@ -53,27 +53,23 @@
     /// Note that the user experience will depend on how the client implements
     /// these message styles - and not all applications implement all message
     /// styles.
-    pub fn send(&self, style: PamMessageStyle, msg: &str) -> PamResult<Option<&CStr>> {
-        let mut resp_ptr: *const PamResponse = ptr::null();
+    pub fn send(&self, style: MessageStyle, msg: &str) -> PamResult<Option<&CStr>> {
+        let mut resp_ptr: *const Response = ptr::null();
         let msg_cstr = CString::new(msg).unwrap();
-        let msg = PamMessage {
+        let msg = Message {
             msg_style: style,
             msg: msg_cstr.as_ptr(),
         };
-
         let ret = (self.0.conv)(1, &&msg, &mut resp_ptr, self.0.appdata_ptr);
+        ErrorCode::result_from(ret)?;
 
-        if PamResultCode::PAM_SUCCESS == ret {
-            // PamResponse.resp is null for styles that don't return user input like PAM_TEXT_INFO
-            let response = unsafe { (*resp_ptr).resp };
-            if response.is_null() {
-                Ok(None)
-            } else {
-                Ok(Some(unsafe { CStr::from_ptr(response) }))
+        let result = unsafe {
+            match (*resp_ptr).resp {
+                p if p.is_null() => None,
+                p => Some(CStr::from_ptr(p)),
             }
-        } else {
-            Err(ret)
-        }
+        };
+        Ok(result)
     }
 }
 
@@ -81,7 +77,7 @@
     type Raw = Inner;
 
     fn type_id() -> crate::items::ItemType {
-        crate::items::ItemType::Conv
+        crate::items::ItemType::Conversation
     }
 
     unsafe fn from_raw(raw: *const Self::Raw) -> Self {
--- a/src/items.rs	Sun May 04 00:58:04 2025 -0400
+++ b/src/items.rs	Sun May 04 02:56:55 2025 -0400
@@ -1,7 +1,12 @@
-use std::ffi::CStr;
+use num_derive::{FromPrimitive, ToPrimitive};
+use num_traits::{FromPrimitive, ToPrimitive};
+use std::ffi::{c_int, CStr};
 
-#[repr(u32)]
+#[derive(FromPrimitive, ToPrimitive)]
+#[repr(i32)]
 pub enum ItemType {
+    /// Unset. This should never be used.
+    Unset = 0,
     /// The service name
     Service = 1,
     /// The user name
@@ -9,15 +14,15 @@
     /// The tty name
     Tty = 3,
     /// The remote host name
-    RHost = 4,
+    RemoteHost = 4,
     /// The pam_conv structure
-    Conv = 5,
+    Conversation = 5,
     /// The authentication token (password)
     AuthTok = 6,
     /// The old authentication token
     OldAuthTok = 7,
     /// The remote user name
-    RUser = 8,
+    RemoteUser = 8,
     /// the prompt for getting a username
     UserPrompt = 9,
     /// app supplied function to override failure delays
@@ -30,6 +35,18 @@
     AuthTokType = 13,
 }
 
+impl From<c_int> for ItemType {
+    fn from(value: c_int) -> Self {
+        Self::from_i32(value).unwrap_or(Self::Unset)
+    }
+}
+
+impl From<ItemType> for c_int {
+    fn from(val: ItemType) -> Self {
+        val.to_i32().unwrap_or(0)
+    }
+}
+
 // A type that can be requested by `pam::Handle::get_item`.
 pub trait Item {
     /// The `repr(C)` type that is returned (by pointer) by the underlying `pam_get_item` function.
@@ -83,9 +100,9 @@
 cstr_item!(Service);
 cstr_item!(User);
 cstr_item!(Tty);
-cstr_item!(RHost);
+cstr_item!(RemoteHost);
 // Conv
 cstr_item!(AuthTok);
 cstr_item!(OldAuthTok);
-cstr_item!(RUser);
+cstr_item!(RemoteUser);
 cstr_item!(UserPrompt);
--- a/src/macros.rs	Sun May 04 00:58:04 2025 -0400
+++ b/src/macros.rs	Sun May 04 02:56:55 2025 -0400
@@ -7,10 +7,10 @@
 /// Here is full example of a PAM module that would authenticate and authorize everybody:
 ///
 /// ```
-/// #[macro_use] extern crate pam;
+/// #[macro_use] extern crate nonstick;
 ///
-/// use pam::module::{PamHooks, PamHandle};
-/// use pam::constants::{PamResultCode, PamFlag};
+/// use nonstick::module::{PamHooks, PamHandle};
+/// use nonstick::constants::{PamResult, Flags};
 /// use std::ffi::CStr;
 ///
 /// # fn main() {}
@@ -18,14 +18,14 @@
 /// pam_hooks!(MyPamModule);
 ///
 /// impl PamHooks for MyPamModule {
-///    fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
-///        println!("Everybody is authenticated!");
-///        PamResultCode::PAM_SUCCESS
+///    fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
+///        println!("Everybody is authorized!");
+///        Ok(())
 ///    }
 ///
-///    fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
-///        println!("Everybody is authorized!");
-///        PamResultCode::PAM_SUCCESS
+///    fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
+///        println!("Everybody is authenticated!");
+///        Ok(())
 ///    }
 /// }
 /// ```
@@ -36,7 +36,7 @@
         mod pam_hooks_scope {
             use std::ffi::CStr;
             use std::os::raw::{c_char, c_int};
-            use $crate::constants::{PamFlag, PamResultCode};
+            use $crate::constants::{Flags, ErrorCode};
             use $crate::module::{PamHandle, PamHooks};
 
             fn extract_argv<'a>(argc: c_int, argv: *const *const c_char) -> Vec<&'a CStr> {
@@ -48,88 +48,72 @@
             #[no_mangle]
             pub extern "C" fn pam_sm_acct_mgmt(
                 pamh: &mut PamHandle,
-                flags: PamFlag,
+                flags: Flags,
                 argc: c_int,
                 argv: *const *const c_char,
-            ) -> PamResultCode {
+            ) -> c_int {
                 let args = extract_argv(argc, argv);
-                super::$ident::acct_mgmt(pamh, args, flags)
+                ErrorCode::result_to_c(super::$ident::acct_mgmt(pamh, args, flags))
             }
 
             #[no_mangle]
             pub extern "C" fn pam_sm_authenticate(
                 pamh: &mut PamHandle,
-                flags: PamFlag,
+                flags: Flags,
                 argc: c_int,
                 argv: *const *const c_char,
-            ) -> PamResultCode {
+            ) -> c_int {
                 let args = extract_argv(argc, argv);
-                super::$ident::sm_authenticate(pamh, args, flags)
+                ErrorCode::result_to_c(super::$ident::sm_authenticate(pamh, args, flags))
             }
 
             #[no_mangle]
             pub extern "C" fn pam_sm_chauthtok(
                 pamh: &mut PamHandle,
-                flags: PamFlag,
+                flags: Flags,
                 argc: c_int,
                 argv: *const *const c_char,
-            ) -> PamResultCode {
+            ) -> c_int {
                 let args = extract_argv(argc, argv);
-                super::$ident::sm_chauthtok(pamh, args, flags)
+                ErrorCode::result_to_c(super::$ident::sm_chauthtok(pamh, args, flags))
             }
 
             #[no_mangle]
             pub extern "C" fn pam_sm_close_session(
                 pamh: &mut PamHandle,
-                flags: PamFlag,
+                flags: Flags,
                 argc: c_int,
                 argv: *const *const c_char,
-            ) -> PamResultCode {
+            ) -> c_int {
                 let args = extract_argv(argc, argv);
-                super::$ident::sm_close_session(pamh, args, flags)
+                ErrorCode::result_to_c(super::$ident::sm_close_session(pamh, args, flags))
             }
 
             #[no_mangle]
             pub extern "C" fn pam_sm_open_session(
                 pamh: &mut PamHandle,
-                flags: PamFlag,
+                flags: Flags,
                 argc: c_int,
                 argv: *const *const c_char,
-            ) -> PamResultCode {
+            ) -> c_int {
                 let args = extract_argv(argc, argv);
-                super::$ident::sm_open_session(pamh, args, flags)
+                ErrorCode::result_to_c(super::$ident::sm_open_session(pamh, args, flags))
             }
 
             #[no_mangle]
             pub extern "C" fn pam_sm_setcred(
                 pamh: &mut PamHandle,
-                flags: PamFlag,
+                flags: Flags,
                 argc: c_int,
                 argv: *const *const c_char,
-            ) -> PamResultCode {
+            ) -> c_int {
                 let args = extract_argv(argc, argv);
-                super::$ident::sm_setcred(pamh, args, flags)
+                ErrorCode::result_to_c(super::$ident::sm_setcred(pamh, args, flags))
             }
         }
     };
 }
 
-#[macro_export]
-macro_rules! pam_try {
-    ($r:expr) => {
-        match $r {
-            Ok(t) => t,
-            Err(e) => return e,
-        }
-    };
-    ($r:expr, $e:expr) => {
-        match $r {
-            Ok(t) => t,
-            Err(_) => return $e,
-        }
-    };
-}
-
 #[cfg(test)]
 pub mod test {
     use crate::module::PamHooks;
--- 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)
     }
 }