view src/handle.rs @ 68:e4e7d68234d0 default tip

Added tag v0.0.6 for changeset 71e432a213ee
author Paul Fisher <paul@pfish.zone>
date Tue, 27 May 2025 16:40:49 -0400
parents a674799a5cd3
children
line wrap: on
line source

//! The wrapper types and traits for handles into the PAM library.
use crate::constants::{ErrorCode, Result};
use crate::items::{Item, ItemType};
use crate::{memory, pam_ffi};
use libc::c_char;
use secure_string::SecureString;
use std::ffi::{c_int, CString};
use std::mem;

/// Features of a PAM handle that are available to applications and modules.
///
/// You probably want [`LibPamHandle`]. This trait is intended to allow creating
/// mock PAM handle types used for testing PAM modules and applications.
pub trait PamHandle {
    /// Retrieves the name of the user who is authenticating or logging in.
    ///
    /// This is effectively like `handle.get_item::<Item::User>()`.
    /// See the [`pam_get_user` manual page][man]
    /// or [`pam_get_user` in the Module Writer's Guide][mwg].
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nonstick::PamHandle;
    /// # fn _doc(handle: &impl PamHandle) -> Result<(), Box<dyn std::error::Error>> {
    /// // Get the username using the default prompt.
    /// let user = handle.get_user(None)?;
    /// // Get the username using a custom prompt.
    /// let user = handle.get_user(Some("who ARE you even???"))?;
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html
    /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_user
    fn get_user(&self, prompt: Option<&str>) -> Result<String>;

    /// Retrieves the authentication token from the user.
    ///
    /// This is essentially like `handle.get_item::<Item::AuthTok>()`.
    ///
    /// See the [`pam_get_authtok` manual page][man]
    /// or [`pam_get_item` in the Module Writer's Guide][mwg].
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nonstick::PamHandle;
    /// # fn _doc(handle: &impl PamHandle) -> Result<(), Box<dyn std::error::Error>> {
    /// // Get the user's password using the default prompt.
    /// let pass = handle.get_authtok(None)?;
    /// // Get the user's password using a custom prompt.
    /// let pass = handle.get_authtok(Some("Reveal your secrets!"))?;
    /// Ok(())
    /// # }
    /// ```
    ///
    /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html
    /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item
    fn get_authtok(&self, prompt: Option<&str>) -> Result<SecureString>;

    /// Retrieves an [Item] that has been set, possibly by the PAM client.
    ///
    /// These items are *references to PAM memory*
    /// which are *owned by the PAM session*
    /// and you should never modify them.
    ///
    /// See the [`pam_get_item` manual page][man]
    /// or [`pam_get_item` in the Module Writer's Guide][mwg].
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use nonstick::PamHandle;
    /// use nonstick::items::Service;
    ///
    /// # fn _doc(pam_handle: &impl PamHandle) -> Result<(), Box<dyn std::error::Error>> {
    /// let svc: Option<Service> = pam_handle.get_item()?;
    /// match svc {
    ///     Some(name) => eprintln!("The calling service name is {:?}", name.to_string_lossy()),
    ///     None => eprintln!("Who knows what the calling service is?"),
    /// }
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html
    /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item
    fn get_item<T: Item>(&self) -> Result<Option<T>>;

    /// Sets an item in the PAM context. It can be retrieved using [`get_item`](Self::get_item).
    ///
    /// See the [`pam_set_item` manual page][man]
    /// or [`pam_set_item` in the Module Writer's Guide][mwg].
    ///
    /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html
    /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_item
    fn set_item<T: Item>(&mut self, item: T) -> Result<()>;

    /// Closes the PAM session on an owned PAM handle.
    ///
    /// This should be called with the result of the application's last call
    /// into PAM services. Since this is only applicable to *owned* PAM handles,
    /// a PAM module should never call this (and it will never be handed
    /// an owned `PamHandle` that it can `close`).
    ///
    /// See the [`pam_end` manual page][man] for more information.
    ///
    /// ```no_run
    /// # use nonstick::PamHandle;
    /// # use std::error::Error;
    /// # fn _doc(handle: impl PamHandle, auth_result: nonstick::Result<()>) -> Result<(), Box<dyn Error>> {
    /// // Earlier: authentication was performed and the result was stored
    /// // into auth_result.
    /// handle.close(auth_result)?;
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// [man]: https://www.man7.org/linux/man-pages/man3/pam_end.3.html
    fn close(self, status: Result<()>) -> Result<()>;
}

/// Functionality of a PAM handle that can be expected by a PAM module.
///
/// If you are not writing a PAM module (e.g., you are writing an application),
/// you should not use any of the functionality exposed by this trait.
///
/// Like [`PamHandle`], this is intended to allow creating mock implementations
/// of PAM for testing PAM modules.
pub trait PamModuleHandle: PamHandle {
    /// Gets some pointer, identified by `key`, that has been set previously
    /// using [`set_data`](Self::set_data).
    ///
    /// The data, if present, is still owned by the current PAM session.
    ///
    /// See the [`pam_get_data` manual page][man]
    /// or [`pam_get_data` in the Module Writer's Guide][mwg].
    ///
    /// # Safety
    ///
    /// The data stored under the provided key must be of type `T`,
    /// otherwise you'll get back a completely invalid `&T`
    /// and further behavior is undefined.
    ///
    /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html
    /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_data
    unsafe fn get_data<T>(&self, key: &str) -> Result<Option<&T>>;

    /// Stores a pointer that can be retrieved later with [`get_data`](Self::get_data).
    ///
    /// This data is accessible to this module and other PAM modules
    /// (using the provided `key`), but is *not* accessible to the application.
    /// The PAM session takes ownership of the data, and it will be dropped
    /// when the session ends.
    ///
    /// See the [`pam_set_data` manual page][man]
    /// or [`pam_set_data` in the Module Writer's Guide][mwg].
    ///
    /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html
    /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_data
    fn set_data<T>(&mut self, key: &str, data: Box<T>) -> Result<()>;
}

/// A [`PamHandle`] backed by `libpam`, i.e., a real PAM handle.
///
/// This structure wraps an opaque PAM handle and gives you a nice Rusty
/// interface to use PAM.
#[repr(C)]
pub struct LibPamHandle(pam_ffi::Handle);

impl LibPamHandle {
    /// Converts a pointer passed from PAM into a borrowed handle.
    ///
    /// # Safety
    ///
    /// It is your responsibility to provide a valid pointer.
    pub unsafe fn from_ptr<'a>(ptr: *mut libc::c_void) -> &'a mut LibPamHandle {
        &mut *(ptr as *mut LibPamHandle)
    }
}

impl Drop for LibPamHandle {
    /// Ends the PAM session with a zero error code.
    fn drop(&mut self) {
        unsafe {
            pam_ffi::pam_end(&mut self.0, 0);
        }
    }
}

impl PamHandle for LibPamHandle {
    fn get_user(&self, prompt: Option<&str>) -> crate::Result<String> {
        let prompt = memory::option_cstr(prompt)?;
        let mut output: *const c_char = std::ptr::null_mut();
        let ret = unsafe {
            pam_ffi::pam_get_user(&self.0, &mut output, memory::prompt_ptr(prompt.as_ref()))
        };
        ErrorCode::result_from(ret)?;
        memory::copy_pam_string(output)
    }

    fn get_authtok(&self, prompt: Option<&str>) -> crate::Result<SecureString> {
        let prompt = memory::option_cstr(prompt)?;
        let mut output: *const c_char = std::ptr::null_mut();
        let res = unsafe {
            pam_ffi::pam_get_authtok(
                &self.0,
                ItemType::AuthTok.into(),
                &mut output,
                memory::prompt_ptr(prompt.as_ref()),
            )
        };
        ErrorCode::result_from(res)?;
        memory::copy_pam_string(output).map(SecureString::from)
    }

    fn get_item<T: Item>(&self) -> crate::Result<Option<T>> {
        let mut ptr: *const libc::c_void = std::ptr::null();
        let out = unsafe {
            let ret = pam_ffi::pam_get_item(&self.0, T::type_id().into(), &mut ptr);
            ErrorCode::result_from(ret)?;
            let typed_ptr: *const T::Raw = ptr.cast();
            match typed_ptr.is_null() {
                true => None,
                false => Some(T::from_raw(typed_ptr)),
            }
        };
        Ok(out)
    }

    fn set_item<T: Item>(&mut self, item: T) -> crate::Result<()> {
        let ret = unsafe {
            pam_ffi::pam_set_item(&mut self.0, T::type_id().into(), item.into_raw().cast())
        };
        ErrorCode::result_from(ret)
    }

    fn close(mut self, status: Result<()>) -> Result<()> {
        let result = unsafe { pam_ffi::pam_end(&mut self.0, ErrorCode::result_to_c(status)) };
        // Since we've already `pam_end`ed this session, we don't want it to be
        // double-freed on drop.
        mem::forget(self);
        ErrorCode::result_from(result)
    }
}

impl PamModuleHandle for LibPamHandle {
    unsafe fn get_data<T>(&self, key: &str) -> crate::Result<Option<&T>> {
        let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?;
        let mut ptr: *const libc::c_void = std::ptr::null();
        ErrorCode::result_from(pam_ffi::pam_get_data(&self.0, c_key.as_ptr(), &mut ptr))?;
        match ptr.is_null() {
            true => Ok(None),
            false => {
                let typed_ptr = ptr.cast();
                Ok(Some(&*typed_ptr))
            }
        }
    }

    fn set_data<T>(&mut self, key: &str, data: Box<T>) -> crate::Result<()> {
        let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?;
        let ret = unsafe {
            pam_ffi::pam_set_data(
                &mut self.0,
                c_key.as_ptr(),
                Box::into_raw(data).cast(),
                set_data_cleanup::<T>,
            )
        };
        ErrorCode::result_from(ret)
    }
}

/// 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());
    }
}