diff src/libpam/handle.rs @ 141:a508a69c068a

Remove a lot of Results from functions. Many functions are documented to only return failing Results when given improper inputs or when there is a memory allocation failure (which can be verified by looking at the source). In cases where we know our input is correct, we don't need to check for memory allocation errors for the same reason that Rust doesn't do so when you, e.g., create a new Vec.
author Paul Fisher <paul@pfish.zone>
date Sat, 05 Jul 2025 17:16:56 -0400
parents 6c1e1bdb4164
children ebb71a412b58
line wrap: on
line diff
--- a/src/libpam/handle.rs	Sat Jul 05 17:11:33 2025 -0400
+++ b/src/libpam/handle.rs	Sat Jul 05 17:16:56 2025 -0400
@@ -1,4 +1,4 @@
-use super::conversation::LibPamConversation;
+use super::conversation::{OwnedConversation, PamConv};
 use crate::constants::{ErrorCode, Result};
 use crate::conv::Exchange;
 use crate::environ::EnvironMapMut;
@@ -10,32 +10,18 @@
     guide, linklist, stdlinks, Conversation, EnvironMap, Flags, PamHandleApplication,
     PamHandleModule,
 };
+use libpam_sys_helpers::constants;
 use num_enum::{IntoPrimitive, TryFromPrimitive};
 use std::cell::Cell;
 use std::ffi::{c_char, c_int, CString};
-
+use std::mem::ManuallyDrop;
 use std::ptr;
 use std::ptr::NonNull;
 
-/// Owner for a PAM handle.
-pub struct LibPamHandle(pub NonNull<libpam_sys::pam_handle>);
-
-impl AsRef<libpam_sys::pam_handle> for LibPamHandle {
-    fn as_ref(&self) -> &libpam_sys::pam_handle {
-        unsafe { self.0.as_ref() }
-    }
-}
-
-impl AsMut<libpam_sys::pam_handle> for LibPamHandle {
-    fn as_mut(&mut self) -> &mut libpam_sys::pam_handle {
-        unsafe { self.0.as_mut() }
-    }
-}
-
 /// An owned PAM handle.
-pub struct OwnedLibPamHandle<'a> {
+pub struct OwnedLibPamHandle<C: Conversation> {
     /// The handle itself.
-    handle: LibPamHandle,
+    handle: ManuallyDrop<RawPamHandle>,
     /// The last return value from the handle.
     last_return: Cell<Result<()>>,
     /// If set, the Conversation that this PAM handle owns.
@@ -45,7 +31,7 @@
     /// conversation. Linux-PAM copies the contents of the `pam_conv` struct
     /// that you pass in to `pam_start`, but OpenPAM uses the pointer itself,
     /// so you have to keep it in one place.
-    conversation: Option<Box<LibPamConversation<'a>>>,
+    conversation: Box<OwnedConversation<C>>,
 }
 
 #[derive(Debug, PartialEq)]
@@ -68,12 +54,12 @@
         self
     }
     /// Builds a PAM handle and starts the transaction.
-    pub fn build(self, conv: &impl Conversation) -> Result<OwnedLibPamHandle> {
+    pub fn build(self, conv: impl Conversation) -> Result<OwnedLibPamHandle<impl Conversation>> {
         OwnedLibPamHandle::start(self.service_name, self.username, conv)
     }
 }
 
-impl OwnedLibPamHandle<'_> {
+impl<C: Conversation> OwnedLibPamHandle<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
@@ -92,12 +78,8 @@
         }
     }
 
-    fn start(
-        service_name: String,
-        username: Option<String>,
-        conversation: &impl Conversation,
-    ) -> Result<Self> {
-        let conv = Box::new(LibPamConversation::wrap(conversation));
+    fn start(service_name: String, username: Option<String>, conversation: C) -> Result<Self> {
+        let conv = Box::new(OwnedConversation::new(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());
 
@@ -108,7 +90,7 @@
             libpam_sys::pam_start(
                 service_cstr.as_ptr(),
                 username_cstr,
-                (conv.as_ref() as *const LibPamConversation)
+                (conv.as_ref() as *const OwnedConversation<C>)
                     .cast_mut()
                     .cast(),
                 &mut handle,
@@ -117,35 +99,37 @@
         ErrorCode::result_from(result)?;
         let handle = NonNull::new(handle).ok_or(ErrorCode::BufferError)?;
         Ok(Self {
-            handle: LibPamHandle(handle),
+            handle: ManuallyDrop::new(RawPamHandle(handle)),
             last_return: Cell::new(Ok(())),
-            conversation: Some(conv),
+            conversation: conv,
         })
     }
+
+    /// "Quietly" closes the PAM session on an owned PAM handle.
+    ///
+    /// This internally calls `pam_end` with the appropriate error code.
+    ///
+    /// # References
+    #[doc = linklist!(pam_end: adg, _std)]
+    ///
+    #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")]
+    #[doc = stdlinks!(3 pam_end)]
+
+    fn end_quiet(self) {}
 }
 
-impl PamHandleApplication for OwnedLibPamHandle<'_> {
-    fn authenticate(&mut self, flags: Flags) -> Result<()> {
-        let ret =
-            unsafe { libpam_sys::pam_authenticate(self.handle.as_mut(), flags.bits() as c_int) };
-        let result = ErrorCode::result_from(ret);
-        self.last_return.set(result);
-        result
-    }
+macro_rules! wrap {
+    (fn $name:ident { $pam_func:ident }) => {
+        fn $name(&mut self, flags: Flags) -> Result<()> {
+            ErrorCode::result_from(unsafe { libpam_sys::$pam_func(self.0.as_mut(), flags.bits()) })
+        }
+    };
+}
 
-    fn account_management(&mut self, flags: Flags) -> Result<()> {
-        let ret = unsafe { libpam_sys::pam_acct_mgmt(self.handle.as_mut(), 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 { libpam_sys::pam_chauthtok(self.handle.as_mut(), flags.bits() as c_int) };
-        let result = ErrorCode::result_from(ret);
-        self.last_return.set(result);
-        result
-    }
+impl PamHandleApplication for RawPamHandle {
+    wrap!(fn authenticate { pam_authenticate });
+    wrap!(fn account_management { pam_acct_mgmt });
+    wrap!(fn change_authtok { pam_chauthtok });
 }
 
 // TODO: pam_authenticate - app
@@ -158,7 +142,7 @@
 //       pam_getenv - shared
 //       pam_getenvlist - shared
 
-impl Drop for OwnedLibPamHandle<'_> {
+impl<C: Conversation> Drop for OwnedLibPamHandle<C> {
     /// Closes the PAM session on an owned PAM handle.
     ///
     /// This internally calls `pam_end` with the appropriate error code.
@@ -171,199 +155,13 @@
     fn drop(&mut self) {
         unsafe {
             libpam_sys::pam_end(
-                self.handle.as_mut(),
+                self.handle.raw_mut(),
                 ErrorCode::result_to_c(self.last_return.get()),
             );
         }
     }
 }
 
-/// Macro to implement getting/setting a CStr-based item.
-macro_rules! cstr_item {
-    (get = $getter:ident, item = $item_type:path) => {
-        fn $getter(&self) -> Result<Option<String>> {
-            unsafe { self.get_cstr_item($item_type) }
-        }
-    };
-    (set = $setter:ident, item = $item_type:path) => {
-        fn $setter(&mut self, value: Option<&str>) -> Result<()> {
-            unsafe { self.set_cstr_item($item_type, value) }
-        }
-    };
-}
-
-impl PamShared for LibPamHandle {
-    #[cfg(any())]
-    fn log(&self, level: Level, loc: Location<'_>, entry: &str) {
-        let entry = match CString::new(entry).or_else(|_| CString::new(dbg!(entry))) {
-            Ok(cstr) => cstr,
-            _ => return,
-        };
-        #[cfg(pam_impl = "linux-pam")]
-        {
-            _ = loc;
-            // SAFETY: We're calling this function with a known value.
-            unsafe {
-                libpam_sys::pam_syslog(self, level as c_int, "%s\0".as_ptr().cast(), entry.as_ptr())
-            }
-        }
-        #[cfg(pam_impl = "openpam")]
-        {
-            let func = CString::new(loc.function).unwrap_or(CString::default());
-            // SAFETY: We're calling this function with a known value.
-            unsafe {
-                libpam_sys::_openpam_log(
-                    level as c_int,
-                    func.as_ptr(),
-                    "%s\0".as_ptr().cast(),
-                    entry.as_ptr(),
-                )
-            }
-        }
-    }
-
-    fn log(&self, _level: Level, _loc: Location<'_>, _entry: &str) {}
-
-    fn username(&mut self, prompt: Option<&str>) -> Result<String> {
-        let prompt = memory::option_cstr(prompt)?;
-        let mut output: *const c_char = ptr::null();
-        let ret = unsafe {
-            libpam_sys::pam_get_user(
-                self.as_mut(),
-                &mut output,
-                memory::prompt_ptr(prompt.as_ref()),
-            )
-        };
-        ErrorCode::result_from(ret)?;
-        unsafe { memory::copy_pam_string(output) }
-            .transpose()
-            .unwrap_or(Err(ErrorCode::ConversationError))
-    }
-
-    fn environ(&self) -> impl EnvironMap {
-        LibPamEnviron::new(self)
-    }
-
-    fn environ_mut(&mut self) -> impl EnvironMapMut {
-        LibPamEnvironMut::new(self)
-    }
-
-    cstr_item!(get = user_item, item = ItemType::User);
-    cstr_item!(set = set_user_item, item = ItemType::User);
-    cstr_item!(get = service, item = ItemType::Service);
-    cstr_item!(set = set_service, item = ItemType::Service);
-    cstr_item!(get = user_prompt, item = ItemType::UserPrompt);
-    cstr_item!(set = set_user_prompt, item = ItemType::UserPrompt);
-    cstr_item!(get = tty_name, item = ItemType::Tty);
-    cstr_item!(set = set_tty_name, item = ItemType::Tty);
-    cstr_item!(get = remote_user, item = ItemType::RemoteUser);
-    cstr_item!(set = set_remote_user, item = ItemType::RemoteUser);
-    cstr_item!(get = remote_host, item = ItemType::RemoteHost);
-    cstr_item!(set = set_remote_host, item = ItemType::RemoteHost);
-    cstr_item!(set = set_authtok_item, item = ItemType::AuthTok);
-    cstr_item!(set = set_old_authtok_item, item = ItemType::OldAuthTok);
-}
-
-impl Conversation for LibPamHandle {
-    fn communicate(&self, messages: &[Exchange]) {
-        match self.conversation_item() {
-            Ok(conv) => conv.communicate(messages),
-            Err(e) => {
-                for msg in messages {
-                    msg.set_error(e)
-                }
-            }
-        }
-    }
-}
-
-impl PamHandleModule for LibPamHandle {
-    #[cfg(any(pam_impl = "LinuxPam", pam_impl = "OpenPam"))]
-    fn authtok(&mut self, prompt: Option<&str>) -> Result<String> {
-        let prompt = memory::option_cstr(prompt)?;
-        let mut output: *const c_char = ptr::null_mut();
-        // SAFETY: We're calling this with known-good values.
-        let res = unsafe {
-            libpam_sys::pam_get_authtok(
-                self.as_mut(),
-                ItemType::AuthTok.into(),
-                &mut output,
-                memory::prompt_ptr(prompt.as_ref()),
-            )
-        };
-        ErrorCode::result_from(res)?;
-        // SAFETY: We got this string from PAM.
-        unsafe { memory::copy_pam_string(output) }
-            .transpose()
-            .unwrap_or(Err(ErrorCode::ConversationError))
-    }
-
-    #[cfg(not(any(pam_impl = "LinuxPam", pam_impl = "OpenPam")))]
-    fn authtok(&mut self, prompt: Option<&str>) -> Result<String> {
-        Err(ErrorCode::ConversationError)
-    }
-
-    cstr_item!(get = authtok_item, item = ItemType::AuthTok);
-    cstr_item!(get = old_authtok_item, item = ItemType::OldAuthTok);
-}
-
-/// 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 set_data_cleanup<T>(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) {
-    unsafe {
-        let _data: Box<T> = Box::from_raw(c_data.cast());
-    }
-}
-
-impl LibPamHandle {
-    /// Gets a C string item.
-    ///
-    /// # Safety
-    ///
-    /// You better be requesting an item which is a C string.
-    unsafe fn get_cstr_item(&self, item_type: ItemType) -> Result<Option<String>> {
-        let mut output = ptr::null();
-        let ret =
-            unsafe { libpam_sys::pam_get_item(self.as_ref(), item_type as c_int, &mut output) };
-        ErrorCode::result_from(ret)?;
-        memory::copy_pam_string(output.cast())
-    }
-
-    /// Sets a C string item.
-    ///
-    /// # Safety
-    ///
-    /// You better be setting an item which is a C string.
-    unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&str>) -> Result<()> {
-        let data_str = memory::option_cstr(data)?;
-        let ret = unsafe {
-            libpam_sys::pam_set_item(
-                self.as_mut(),
-                item_type as c_int,
-                memory::prompt_ptr(data_str.as_ref()).cast(),
-            )
-        };
-        ErrorCode::result_from(ret)
-    }
-
-    /// Gets the `PAM_CONV` item from the handle.
-    fn conversation_item(&self) -> Result<&mut LibPamConversation<'_>> {
-        let output: *mut LibPamConversation = ptr::null_mut();
-        let result = unsafe {
-            libpam_sys::pam_get_item(
-                self.as_ref(),
-                ItemType::Conversation.into(),
-                &mut output.cast_const().cast(),
-            )
-        };
-        ErrorCode::result_from(result)?;
-        // SAFETY: We got this result from PAM, and we're checking if it's null.
-        unsafe { output.as_mut() }.ok_or(ErrorCode::ConversationError)
-    }
-}
-
 macro_rules! delegate {
     // First have the kind that save the result after delegation.
     (fn $meth:ident(&self $(, $param:ident: $typ:ty)*) -> Result<$ret:ty>) => {
@@ -405,7 +203,7 @@
     result.as_ref().map(drop).map_err(|&e| e)
 }
 
-impl PamShared for OwnedLibPamHandle<'_> {
+impl<C: Conversation> PamShared for OwnedLibPamHandle<C> {
     delegate!(fn log(&self, level: Level, location: Location<'_>, entry: &str) -> ());
     delegate!(fn environ(&self) -> impl EnvironMap);
     delegate!(fn environ_mut(&mut self) -> impl EnvironMapMut);
@@ -420,6 +218,290 @@
     delegate!(set = set_old_authtok_item);
 }
 
+/// Macro to implement getting/setting a CStr-based item.
+macro_rules! cstr_item {
+    (get = $getter:ident, item = $item_type:path) => {
+        fn $getter(&self) -> Result<Option<String>> {
+            unsafe { self.get_cstr_item($item_type) }
+        }
+    };
+    (set = $setter:ident, item = $item_type:path) => {
+        fn $setter(&mut self, value: Option<&str>) -> Result<()> {
+            unsafe { self.set_cstr_item($item_type, value) }
+        }
+    };
+}
+
+/// An owned variation of a basic PAM handle.
+///
+/// This is the most basic version of a wrapped PAM handle. It's mostly used
+/// as the inside of the [`OwnedLibPamHandle`], but can also be used to "adopt"
+/// a PAM handle created by another library.
+///
+/// If [`Self::end`] is not called, this will always call `pam_end` reporting
+/// successful completion.
+pub struct RawPamHandle(NonNull<libpam_sys::pam_handle>);
+
+impl RawPamHandle {
+    /// Takes ownership of the pointer to the given PAM handle.
+    ///
+    /// **Do not use this just to get a reference to a PAM handle.**
+    ///
+    /// # Safety
+    ///
+    /// - The pointer must point to a valid PAM handle.
+    /// - The conversation associated with the handle must remain valid
+    ///   for as long as the handle is open.
+    pub unsafe fn from_ptr(handle: NonNull<libpam_sys::pam_handle>) -> Self {
+        Self(handle)
+    }
+
+    /// Ends the transaction, reporting `error_code` to cleanup callbacks.
+    ///
+    /// # References
+    #[doc = linklist!(pam_end: adg, _std)]
+    ///
+    #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")]
+    #[doc = stdlinks!(3 pam_end)]
+    pub fn end(self, result: Result<()>) {
+        let mut me = ManuallyDrop::new(self);
+        unsafe { libpam_sys::pam_end(me.raw_mut(), ErrorCode::result_to_c(result)) };
+    }
+
+    #[cfg_attr(
+        not(pam_impl = "LinuxPam"),
+        doc = "Exactly equivalent to [`Self::end`], except on Linux-PAM."
+    )]
+    #[cfg_attr(
+        pam_impl = "LinuxPam",
+        doc = "Ends the transaction \"quietly\", reporting `error_code` to cleanup callbacks."
+    )]
+    ///
+    /// On Linux-PAM only, this sets the
+    /// [`PAM_DATA_SILENT`](libpam_sys::PAM_DATA_SILENT) flag on the flags
+    /// passed to the cleanup callbacks. This conventionally means that this
+    /// `pam_end` call is occurring on a forked process, and that a session
+    /// may still be open on the parent process, and modules "should not treat
+    /// the call too seriously".
+    ///
+    /// # References
+    #[doc = linklist!(pam_end: adg, _std)]
+    ///
+    #[doc = guide!(adg: "adg-interface-by-app-expected.html#adg-pam_end")]
+    #[doc = stdlinks!(3 pam_end)]
+    pub fn end_quiet(self, result: Result<()>) {
+        let mut me = ManuallyDrop::new(self);
+        let result = ErrorCode::result_to_c(result);
+        #[cfg(pam_impl = "LinuxPam")]
+        let result = result | libpam_sys::PAM_DATA_SILENT;
+        unsafe {
+            libpam_sys::pam_end(me.raw_mut(), result);
+        }
+    }
+
+    /// Consumes this and gives you back the raw PAM handle.
+    pub fn into_inner(self) -> NonNull<libpam_sys::pam_handle> {
+        let me = ManuallyDrop::new(self);
+        me.0
+    }
+
+    /// Gets a reference to the inner PAM handle.
+    pub fn raw_ref(&self) -> &libpam_sys::pam_handle {
+        unsafe { self.0.as_ref() }
+    }
+    /// Gets a mutable reference to the inner PAM handle.
+    pub fn raw_mut(&mut self) -> &mut libpam_sys::pam_handle {
+        unsafe { self.0.as_mut() }
+    }
+}
+
+impl Drop for RawPamHandle {
+    fn drop(&mut self) {
+        unsafe { libpam_sys::pam_end(self.0.as_mut(), 0) };
+    }
+}
+
+impl PamShared for RawPamHandle {
+    #[cfg(any())]
+    fn log(&self, level: Level, loc: Location<'_>, entry: &str) {
+        let entry = match CString::new(entry).or_else(|_| CString::new(dbg!(entry))) {
+            Ok(cstr) => cstr,
+            _ => return,
+        };
+        #[cfg(pam_impl = "linux-pam")]
+        {
+            _ = loc;
+            // SAFETY: We're calling this function with a known value.
+            unsafe {
+                libpam_sys::pam_syslog(self, level as c_int, "%s\0".as_ptr().cast(), entry.as_ptr())
+            }
+        }
+        #[cfg(pam_impl = "openpam")]
+        {
+            let func = CString::new(loc.function).unwrap_or(CString::default());
+            // SAFETY: We're calling this function with a known value.
+            unsafe {
+                libpam_sys::_openpam_log(
+                    level as c_int,
+                    func.as_ptr(),
+                    "%s\0".as_ptr().cast(),
+                    entry.as_ptr(),
+                )
+            }
+        }
+    }
+
+    fn log(&self, _level: Level, _loc: Location<'_>, _entry: &str) {}
+
+    fn username(&mut self, prompt: Option<&str>) -> Result<String> {
+        let prompt = memory::option_cstr(prompt)?;
+        let mut output: *const c_char = ptr::null();
+        let ret = unsafe {
+            libpam_sys::pam_get_user(
+                self.raw_mut(),
+                &mut output,
+                memory::prompt_ptr(prompt.as_ref()),
+            )
+        };
+        ErrorCode::result_from(ret)?;
+        unsafe { memory::copy_pam_string(output) }
+            .transpose()
+            .unwrap_or(Err(ErrorCode::ConversationError))
+    }
+
+    fn environ(&self) -> impl EnvironMap {
+        LibPamEnviron::new(self)
+    }
+
+    fn environ_mut(&mut self) -> impl EnvironMapMut {
+        LibPamEnvironMut::new(self)
+    }
+
+    cstr_item!(get = user_item, item = ItemType::User);
+    cstr_item!(set = set_user_item, item = ItemType::User);
+    cstr_item!(get = service, item = ItemType::Service);
+    cstr_item!(set = set_service, item = ItemType::Service);
+    cstr_item!(get = user_prompt, item = ItemType::UserPrompt);
+    cstr_item!(set = set_user_prompt, item = ItemType::UserPrompt);
+    cstr_item!(get = tty_name, item = ItemType::Tty);
+    cstr_item!(set = set_tty_name, item = ItemType::Tty);
+    cstr_item!(get = remote_user, item = ItemType::RemoteUser);
+    cstr_item!(set = set_remote_user, item = ItemType::RemoteUser);
+    cstr_item!(get = remote_host, item = ItemType::RemoteHost);
+    cstr_item!(set = set_remote_host, item = ItemType::RemoteHost);
+    cstr_item!(set = set_authtok_item, item = ItemType::AuthTok);
+    cstr_item!(set = set_old_authtok_item, item = ItemType::OldAuthTok);
+}
+
+impl Conversation for RawPamHandle {
+    fn communicate(&self, messages: &[Exchange]) {
+        match self.conversation_item() {
+            Ok(conv) => conv.communicate(messages),
+            Err(e) => {
+                for msg in messages {
+                    msg.set_error(e)
+                }
+            }
+        }
+    }
+}
+
+impl PamHandleModule for RawPamHandle {
+    fn authtok(&mut self, prompt: Option<&str>) -> Result<String> {
+        self.get_authtok(prompt, ItemType::AuthTok)
+    }
+
+    fn old_authtok(&mut self, prompt: Option<&str>) -> Result<String> {
+        self.get_authtok(prompt, ItemType::OldAuthTok)
+    }
+
+    cstr_item!(get = authtok_item, item = ItemType::AuthTok);
+    cstr_item!(get = old_authtok_item, item = ItemType::OldAuthTok);
+}
+
+/// 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 set_data_cleanup<T>(_: *const libc::c_void, c_data: *mut libc::c_void, _: c_int) {
+    unsafe {
+        let _data: Box<T> = Box::from_raw(c_data.cast());
+    }
+}
+
+// Implementations of internal functions.
+impl RawPamHandle {
+    #[cfg(any(pam_impl = "LinuxPam", pam_impl = "OpenPam"))]
+    fn get_authtok(&mut self, prompt: Option<&str>, item_type: ItemType) -> Result<String> {
+        let prompt = memory::option_cstr(prompt)?;
+        let mut output: *const c_char = ptr::null_mut();
+        // SAFETY: We're calling this with known-good values.
+        let res = unsafe {
+            libpam_sys::pam_get_authtok(
+                self.raw_mut(),
+                item_type.into(),
+                &mut output,
+                memory::prompt_ptr(prompt.as_ref()),
+            )
+        };
+        ErrorCode::result_from(res)?;
+        // SAFETY: We got this string from PAM.
+        unsafe { memory::copy_pam_string(output) }
+            .transpose()
+            .unwrap_or(Err(ErrorCode::ConversationError))
+    }
+
+    #[cfg(not(any(pam_impl = "LinuxPam", pam_impl = "OpenPam")))]
+    fn get_authtok(&mut self, prompt: Option<&str>, item_type: ItemType) -> Result<String> {
+        Err(ErrorCode::ConversationError)
+    }
+
+    /// Gets a C string item.
+    ///
+    /// # Safety
+    ///
+    /// You better be requesting an item which is a C string.
+    unsafe fn get_cstr_item(&self, item_type: ItemType) -> Result<Option<String>> {
+        let mut output = ptr::null();
+        let ret =
+            unsafe { libpam_sys::pam_get_item(self.raw_ref(), item_type as c_int, &mut output) };
+        ErrorCode::result_from(ret)?;
+        memory::copy_pam_string(output.cast())
+    }
+
+    /// Sets a C string item.
+    ///
+    /// # Safety
+    ///
+    /// You better be setting an item which is a C string.
+    unsafe fn set_cstr_item(&mut self, item_type: ItemType, data: Option<&str>) -> Result<()> {
+        let data_str = memory::option_cstr(data)?;
+        let ret = unsafe {
+            libpam_sys::pam_set_item(
+                self.raw_mut(),
+                item_type as c_int,
+                memory::prompt_ptr(data_str.as_ref()).cast(),
+            )
+        };
+        ErrorCode::result_from(ret)
+    }
+
+    /// Gets the `PAM_CONV` item from the handle.
+    fn conversation_item(&self) -> Result<&PamConv> {
+        let output: *const PamConv = ptr::null_mut();
+        let result = unsafe {
+            libpam_sys::pam_get_item(
+                self.raw_ref(),
+                ItemType::Conversation.into(),
+                &mut output.cast(),
+            )
+        };
+        ErrorCode::result_from(result)?;
+        // SAFETY: We got this result from PAM, and we're checking if it's null.
+        unsafe { output.as_ref() }.ok_or(ErrorCode::ConversationError)
+    }
+}
+
 /// Identifies what is being gotten or set with `pam_get_item`
 /// or `pam_set_item`.
 #[derive(TryFromPrimitive, IntoPrimitive)]
@@ -427,29 +509,33 @@
 #[non_exhaustive] // because C could give us anything!
 pub enum ItemType {
     /// The PAM service name.
-    Service = 1,
+    Service = constants::PAM_SERVICE,
     /// The user's login name.
-    User = 2,
+    User = constants::PAM_USER,
     /// The TTY name.
-    Tty = 3,
+    Tty = constants::PAM_TTY,
     /// The remote host (if applicable).
-    RemoteHost = 4,
+    RemoteHost = constants::PAM_RHOST,
     /// The conversation struct (not a CStr-based item).
-    Conversation = 5,
+    Conversation = constants::PAM_CONV,
     /// The authentication token (password).
-    AuthTok = 6,
+    AuthTok = constants::PAM_AUTHTOK,
     /// The old authentication token (when changing passwords).
-    OldAuthTok = 7,
+    OldAuthTok = constants::PAM_OLDAUTHTOK,
     /// The remote user's name.
-    RemoteUser = 8,
+    RemoteUser = constants::PAM_RUSER,
     /// The prompt shown when requesting a username.
-    UserPrompt = 9,
+    UserPrompt = constants::PAM_USER_PROMPT,
+    #[cfg(feature = "linux-pam-ext")]
     /// App-supplied function to override failure delays.
-    FailDelay = 10,
+    FailDelay = constants::PAM_FAIL_DELAY,
+    #[cfg(feature = "linux-pam-ext")]
     /// X display name.
-    XDisplay = 11,
+    XDisplay = constants::PAM_XDISPLAY,
+    #[cfg(feature = "linux-pam-ext")]
     /// X server authentication data.
-    XAuthData = 12,
+    XAuthData = constants::PAM_XAUTHDATA,
+    #[cfg(feature = "linux-pam-ext")]
     /// The type of `pam_get_authtok`.
-    AuthTokType = 13,
+    AuthTokType = constants::PAM_AUTHTOK_TYPE,
 }