view src/libpam/module.rs @ 171:e27c5c667a5a

Create full new types for return code and flags, separate end to end. This plumbs the ReturnCode and RawFlags types through the places where we call into or are called from PAM. Also adds Sun documentation to the project.
author Paul Fisher <paul@pfish.zone>
date Fri, 25 Jul 2025 20:52:14 -0400
parents 2f5913131295
children 6727cbe56f4a
line wrap: on
line source

use crate::constants::{ErrorCode, RawFlags, Result};
use crate::libpam::handle::LibPamHandle;
use crate::module::PamModule;
use crate::{AuthnFlags, AuthtokAction, BaseFlags, CredAction};
use std::ffi::{c_char, c_int, c_void, CStr};

/// Generates the dynamic library entry points for a PAM module
///
/// 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:
///
/// ```no_run
/// use nonstick::{
///     pam_hooks, ConversationAdapter, AuthnFlags, LibPamTransaction, ModuleClient, PamModule,
///     Result as PamResult,
/// };
/// use std::ffi::CStr;
/// # fn main() {}
///
/// struct MyPamModule;
/// pam_hooks!(MyPamModule);
///
/// impl<T: ModuleClient> PamModule<T> for MyPamModule {
///     fn authenticate(handle: &mut T, args: Vec<&CStr>, flags: AuthnFlags) -> PamResult<()> {
///         let password = handle.authtok(Some("what's your password?".as_ref()))?;
///         let response =
///             format!("If you say your password is {password:?}, who am I to disagree?");
///         handle.info_msg(&response);
///         Ok(())
///     }
///
///     fn account_management(handle: &mut T, args: Vec<&CStr>, flags: AuthnFlags) -> PamResult<()> {
///         let username = handle.username(None)?;
///         let response = format!("Hello {username:?}! I trust you unconditionally.");
///         handle.info_msg(&response);
///         Ok(())
///     }
/// }
/// ```
#[macro_export]
macro_rules! pam_hooks {
    ($ident:ident) => {
        mod _pam_hooks_scope {
            use std::ffi::{c_char, c_int, c_void};
            use $crate::ModuleExporter;
            use $crate::constants::{RawFlags, ReturnCode};

            macro_rules! export {
                ($func:ident) => {
                    #[no_mangle]
                    unsafe extern "C" fn $func(
                        pamh: *mut c_void,
                        flags: RawFlags,
                        argc: c_int,
                        argv: *const *const c_char,
                    ) -> c_int {
                        let ret: ReturnCode = ModuleExporter::$func::<super::$ident>(
                            pamh, flags, argc, argv
                        ).into();
                        ret.into()
                    }
                };
            }

            export!(pam_sm_acct_mgmt);
            export!(pam_sm_authenticate);
            export!(pam_sm_chauthtok);
            export!(pam_sm_close_session);
            export!(pam_sm_open_session);
            export!(pam_sm_setcred);
        }
    };
}

#[doc(hidden)]
pub struct ModuleExporter;

// All of the below are only intended to be called directly from C.
#[allow(clippy::missing_safety_doc)]
impl ModuleExporter {
    pub unsafe fn pam_sm_acct_mgmt<M: PamModule<LibPamHandle>>(
        pamh: *mut c_void,
        flags: RawFlags,
        argc: c_int,
        argv: *const *const c_char,
    ) -> Result<()> {
        let handle = wrap(pamh)?;
        let args = extract_argv(argc, argv);
        M::account_management(handle, args, AuthnFlags::from(flags))
    }

    pub unsafe fn pam_sm_authenticate<M: PamModule<LibPamHandle>>(
        pamh: *mut c_void,
        flags: RawFlags,
        argc: c_int,
        argv: *const *const c_char,
    ) -> Result<()> {
        let handle = wrap(pamh)?;
        let args = extract_argv(argc, argv);
        M::authenticate(handle, args, AuthnFlags::from(flags))
    }

    pub unsafe fn pam_sm_chauthtok<M: PamModule<LibPamHandle>>(
        pamh: *mut c_void,
        flags: RawFlags,
        argc: c_int,
        argv: *const *const c_char,
    ) -> Result<()> {
        let handle = wrap(pamh)?;
        let (action, flags) = AuthtokAction::extract(flags)?;
        let args = extract_argv(argc, argv);
        M::change_authtok(handle, args, action, flags)
    }

    pub unsafe fn pam_sm_close_session<M: PamModule<LibPamHandle>>(
        pamh: *mut c_void,
        flags: RawFlags,
        argc: c_int,
        argv: *const *const c_char,
    ) -> Result<()> {
        let handle = wrap(pamh)?;
        let args = extract_argv(argc, argv);
        M::close_session(handle, args, BaseFlags::from(flags))
    }

    pub unsafe fn pam_sm_open_session<M: PamModule<LibPamHandle>>(
        pamh: *mut c_void,
        flags: RawFlags,
        argc: c_int,
        argv: *const *const c_char,
    ) -> Result<()> {
        let handle = wrap(pamh)?;
        let args = extract_argv(argc, argv);
        M::open_session(handle, args, BaseFlags::from(flags))
    }

    pub unsafe fn pam_sm_setcred<M: PamModule<LibPamHandle>>(
        pamh: *mut c_void,
        flags: RawFlags,
        argc: c_int,
        argv: *const *const c_char,
    ) -> Result<()> {
        let handle = wrap(pamh)?;
        let (action, flags) = CredAction::extract(flags)?;
        let args = extract_argv(argc, argv);
        M::set_credentials(handle, args, action, 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.
unsafe 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()
}

/// Wraps the pointer in a PAM handle, or returns an error if it's null.
///
/// # Safety
///
/// It's up to you to pass a valid handle.
unsafe fn wrap<'a>(handle: *mut c_void) -> Result<&'a mut LibPamHandle> {
    handle.cast::<LibPamHandle>().as_mut().ok_or(ErrorCode::SystemError)
}

#[cfg(test)]
mod tests {
    // Compile-time test that the `pam_hooks` macro compiles.
    use crate::{ModuleClient, PamModule};
    struct Foo;
    impl<T: ModuleClient> PamModule<T> for Foo {}

    pam_hooks!(Foo);
}