view src/libpam/module.rs @ 77:351bdc13005e

Update the libpam module to work with the new structure.
author Paul Fisher <paul@pfish.zone>
date Sun, 08 Jun 2025 01:03:46 -0400
parents c30811b4afae
children 002adfb98c5c
line wrap: on
line source

/// 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:
///
/// ```no_run
/// use nonstick::{Flags, SimpleConversation, OwnedLibPamHandle, PamModule, PamHandleModule, Result as PamResult, pam_hooks};
/// use std::ffi::CStr;
/// # fn main() {}
///
/// struct MyPamModule;
/// pam_hooks!(MyPamModule);
///
/// impl<T: PamHandleModule> PamModule<T> for MyPamModule {
///     fn authenticate(handle: &mut T, args: Vec<&CStr>, flags: Flags) -> PamResult<()> {
///         let password = handle.get_authtok(Some("what's your password?"))?;
///         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: Flags) -> PamResult<()> {
///         let username = handle.get_user(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, CStr};
            use $crate::{ErrorCode, Flags, LibPamHandle, 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 {
                if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } {
                    let args = extract_argv(argc, argv);
                    ErrorCode::result_to_c(super::$ident::account_management(handle, args, flags))
                } else {
                    ErrorCode::Ignore as c_int
                }
            }

            #[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 {
                if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } {
                    let args = extract_argv(argc, argv);
                    ErrorCode::result_to_c(super::$ident::authenticate(handle, args, flags))
                } else {
                    ErrorCode::Ignore as c_int
                }
            }

            #[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 {
                if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } {
                    let args = extract_argv(argc, argv);
                    ErrorCode::result_to_c(super::$ident::change_authtok(handle, args, flags))
                } else {
                    ErrorCode::Ignore as c_int
                }
            }

            #[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 {
                if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } {
                    let args = extract_argv(argc, argv);
                    ErrorCode::result_to_c(super::$ident::close_session(handle, args, flags))
                } else {
                    ErrorCode::Ignore as c_int
                }
            }

            #[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);
                if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } {
                    ErrorCode::result_to_c(super::$ident::open_session(handle, args, flags))
                } else {
                    ErrorCode::Ignore as c_int
                }
            }

            #[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);
                if let Some(handle) = unsafe { pamh.cast::<LibPamHandle>().as_mut() } {
                    ErrorCode::result_to_c(super::$ident::set_credentials(handle, args, flags))
                } else {
                    ErrorCode::Ignore as c_int
                }
            }

            /// 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)]
mod tests {
    // Compile-time test that the `pam_hooks` macro compiles.
    use crate::{PamHandleModule, PamModule};
    struct Foo;
    impl<T: PamHandleModule> PamModule<T> for Foo {}

    pam_hooks!(Foo);
}