view src/module.rs @ 60:05cc2c27334f

The Big Refactor: clean up docs and exports. - Brings the most important symbols in the library to the root with `pub use` statements. - Expands and updates documentation. - Rearranges things extensively to make the external interface nicer and make the structure easier to understand. - Renames a few things (e.g. `Result`).
author Paul Fisher <paul@pfish.zone>
date Wed, 21 May 2025 19:00:51 -0400
parents 3f4a77aa88be
children bbe84835d6db
line wrap: on
line source

//! Functions and types useful for implementing a PAM module.

use crate::constants::{ErrorCode, Flags, Result};
use crate::items::{Item, ItemType};
use crate::memory;
use libc::c_char;
use secure_string::SecureString;
use std::ffi::{c_int, CStr, CString};

use crate::pam_ffi;

/// 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 libc::c_void, c_data: *mut libc::c_void, _: c_int) {
    unsafe {
        let _data: Box<T> = Box::from_raw(c_data.cast());
    }
}

/// An opaque structure pointing to a PAM handle.
#[repr(transparent)]
pub struct PamHandle(*mut libc::c_void);

impl PamHandle {
    /// Gets some value, identified by `key`, that has been set by the module
    /// previously.
    ///
    /// See the [`pam_get_data` manual page](
    /// https://www.man7.org/linux/man-pages/man3/pam_get_data.3.html).
    ///
    /// # Safety
    ///
    /// The data stored under the provided key must be of type `T` otherwise the
    /// behaviour of this function is undefined.
    ///
    /// The data, if present, is owned by the current PAM conversation.
    pub unsafe fn get_data<T>(&self, key: &str) -> 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))
            }
        }
    }

    /// Stores a value that can be retrieved later with `get_data`.
    /// The conversation takes ownership of the data.
    ///
    /// See the [`pam_set_data` manual page](
    /// https://www.man7.org/linux/man-pages/man3/pam_set_data.3.html).
    pub fn set_data<T>(&mut self, key: &str, data: Box<T>) -> Result<()> {
        let c_key = CString::new(key).map_err(|_| ErrorCode::ConversationError)?;
        let ret = unsafe {
            pam_ffi::pam_set_data(
                self.0,
                c_key.as_ptr(),
                Box::into_raw(data).cast(),
                cleanup::<T>,
            )
        };
        ErrorCode::result_from(ret)
    }

    /// Retrieves a value that has been set, possibly by the pam client.
    /// This is particularly useful for getting a `PamConv` reference.
    ///
    /// These items are *references to PAM memory*
    /// which are *owned by the conversation*.
    ///
    /// See the [`pam_get_item` manual page](
    /// https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html).
    pub fn get_item<T: crate::items::Item>(&self) -> 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)
    }

    /// Sets an item in the pam context. It can be retrieved using `get_item`.
    ///
    /// See the [`pam_set_item` manual page](
    /// https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html).
    pub fn set_item<T: Item>(&mut self, item: T) -> Result<()> {
        let ret =
            unsafe { pam_ffi::pam_set_item(self.0, T::type_id().into(), item.into_raw().cast()) };
        ErrorCode::result_from(ret)
    }

    /// Retrieves the name of the user who is authenticating or logging in.
    ///
    /// This is really a specialization of `get_item`.
    ///
    /// See the [`pam_get_user` manual page](
    /// https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html).
    pub fn get_user(&self, prompt: Option<&str>) -> 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)
    }

    /// Retrieves the authentication token from the user.
    ///
    /// This is really a specialization of `get_item`.
    ///
    /// See the [`pam_get_authtok` manual page](
    /// https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html).
    pub fn get_authtok(&self, prompt: Option<&str>) -> 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)
    }
}

impl From<*mut libc::c_void> for PamHandle {
    /// Wraps an internal Handle pointer.
    fn from(value: *mut libc::c_void) -> Self {
        Self(value)
    }
}

/// Trait representing what a PAM module can do.
///
/// By default, all the functions in this trait are ignored.
/// Implement any functions you wish to handle in your module.
/// After implementing this trait, use the [crate::pam_hooks!] macro
/// to export your functions.
///
/// For more information, see [`pam(3)`’s root manual page][manpage]
/// and the [PAM Module Writer’s Guide][module-guide].
///
/// [manpage]: https://www.man7.org/linux/man-pages/man3/pam.3.html
/// [module-guide]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/Linux-PAM_MWG.html
#[allow(unused_variables)]
pub trait PamModule {
    /// This function performs the task of establishing whether the user is permitted to gain access at
    /// this time. It should be understood that the user has previously been validated by an
    /// 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(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
        Err(ErrorCode::Ignore)
    }

    /// This function performs the task of authenticating the user.
    fn sm_authenticate(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
        Err(ErrorCode::Ignore)
    }

    /// This function is used to (re-)set the authentication token of the user.
    ///
    /// The PAM library calls this function twice in succession. The first time with
    /// `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(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
        Err(ErrorCode::Ignore)
    }

    /// This function is called to terminate a session.
    fn sm_close_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
        Err(ErrorCode::Ignore)
    }

    /// This function is called to commence a session.
    fn sm_open_session(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
        Err(ErrorCode::Ignore)
    }

    /// This function performs the task of altering the credentials of the user with respect to the
    /// corresponding authorization scheme. Generally, an authentication module may have access to more
    /// 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(handle: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> Result<()> {
        Err(ErrorCode::Ignore)
    }
}

/// Generates the dynamic library entry points for a [PamModule] implementation.
///
/// Calling `pam_hooks!(SomeType)` on a type that implements [PamModule] will
/// generate the exported `extern "C"` functions that PAM uses to call into
/// your module.
///
/// ## Examples:
///
/// Here is full example of a PAM module that would authenticate and authorize everybody:
///
/// ```
/// use nonstick::{Flags, PamHandle, PamModule, Result as PamResult, pam_hooks};
/// use std::ffi::CStr;
///
/// # fn main() {}
/// struct MyPamModule;
/// pam_hooks!(MyPamModule);
///
/// impl PamModule for MyPamModule {
///     fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
///         // You should use a Conversation to communicate with the user
///         // instead of writing to the console, but this is just an example.
///         eprintln!("Everybody is authorized!");
///         Ok(())
///     }
///
///     fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
///         eprintln!("Everybody is authenticated!");
///         Ok(())
///     }
/// }
/// ```
#[macro_export]
macro_rules! pam_hooks {
    ($ident:ident) => {
        mod _pam_hooks_scope {
            use std::ffi::{c_char, c_int, CStr};
            use $crate::{ErrorCode, Flags, PamModule};

            #[no_mangle]
            extern "C" fn pam_sm_acct_mgmt(
                pamh: *mut libc::c_void,
                flags: Flags,
                argc: c_int,
                argv: *const *const c_char,
            ) -> c_int {
                let args = extract_argv(argc, argv);
                ErrorCode::result_to_c(super::$ident::acct_mgmt(&mut pamh.into(), args, flags))
            }

            #[no_mangle]
            extern "C" fn pam_sm_authenticate(
                pamh: *mut libc::c_void,
                flags: Flags,
                argc: c_int,
                argv: *const *const c_char,
            ) -> c_int {
                let args = extract_argv(argc, argv);
                ErrorCode::result_to_c(super::$ident::sm_authenticate(
                    &mut pamh.into(),
                    args,
                    flags,
                ))
            }

            #[no_mangle]
            extern "C" fn pam_sm_chauthtok(
                pamh: *mut libc::c_void,
                flags: Flags,
                argc: c_int,
                argv: *const *const c_char,
            ) -> c_int {
                let args = extract_argv(argc, argv);
                ErrorCode::result_to_c(super::$ident::sm_chauthtok(&mut pamh.into(), args, flags))
            }

            #[no_mangle]
            extern "C" fn pam_sm_close_session(
                pamh: *mut libc::c_void,
                flags: Flags,
                argc: c_int,
                argv: *const *const c_char,
            ) -> c_int {
                let args = extract_argv(argc, argv);
                ErrorCode::result_to_c(super::$ident::sm_close_session(
                    &mut pamh.into(),
                    args,
                    flags,
                ))
            }

            #[no_mangle]
            extern "C" fn pam_sm_open_session(
                pamh: *mut libc::c_void,
                flags: Flags,
                argc: c_int,
                argv: *const *const c_char,
            ) -> c_int {
                let args = extract_argv(argc, argv);
                ErrorCode::result_to_c(super::$ident::sm_open_session(
                    &mut pamh.into(),
                    args,
                    flags,
                ))
            }

            #[no_mangle]
            extern "C" fn pam_sm_setcred(
                pamh: *mut libc::c_void,
                flags: Flags,
                argc: c_int,
                argv: *const *const c_char,
            ) -> c_int {
                let args = extract_argv(argc, argv);
                ErrorCode::result_to_c(super::$ident::sm_setcred(&mut pamh.into(), args, flags))
            }

            /// Turns `argc`/`argv` into a [Vec] of [CStr]s.
            ///
            /// # Safety
            ///
            /// We use this only with arguments we get from `libpam`, which we kind of have to trust.
            fn extract_argv<'a>(argc: c_int, argv: *const *const c_char) -> Vec<&'a CStr> {
                (0..argc)
                    .map(|o| unsafe { CStr::from_ptr(*argv.offset(o as isize) as *const c_char) })
                    .collect()
            }
        }
    };
}

#[cfg(test)]
pub mod test {
    use crate::module::PamModule;

    struct Foo;
    impl PamModule for Foo {}

    pam_hooks!(Foo);
}