use crate::constants::PamResultCode;
#[macro_export]
macro_rules! pam_hooks {
($ident:ident) => {
pub use self::pam_hooks_scope::*;
mod pam_hooks_scope {
use std::os::raw::{c_char, c_int};
use $crate::constants::{PamFlag, PamResultCode};
use $crate::module::{PamHandle, PamHooks};
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pam_sm_acct_mgmt(
pamh: *mut PamHandle,
flags: PamFlag,
argc: c_int,
argv: *const *const c_char,
) -> PamResultCode {
unsafe {
$crate::macros::invoke_hook(pamh, argc, argv, |pamh, args| {
super::$ident::acct_mgmt(pamh, args, flags)
})
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pam_sm_authenticate(
pamh: *mut PamHandle,
flags: PamFlag,
argc: c_int,
argv: *const *const c_char,
) -> PamResultCode {
unsafe {
$crate::macros::invoke_hook(pamh, argc, argv, |pamh, args| {
super::$ident::sm_authenticate(pamh, args, flags)
})
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pam_sm_chauthtok(
pamh: *mut PamHandle,
flags: PamFlag,
argc: c_int,
argv: *const *const c_char,
) -> PamResultCode {
unsafe {
$crate::macros::invoke_hook(pamh, argc, argv, |pamh, args| {
super::$ident::sm_chauthtok(pamh, args, flags)
})
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pam_sm_close_session(
pamh: *mut PamHandle,
flags: PamFlag,
argc: c_int,
argv: *const *const c_char,
) -> PamResultCode {
unsafe {
$crate::macros::invoke_hook(pamh, argc, argv, |pamh, args| {
super::$ident::sm_close_session(pamh, args, flags)
})
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pam_sm_open_session(
pamh: *mut PamHandle,
flags: PamFlag,
argc: c_int,
argv: *const *const c_char,
) -> PamResultCode {
unsafe {
$crate::macros::invoke_hook(pamh, argc, argv, |pamh, args| {
super::$ident::sm_open_session(pamh, args, flags)
})
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn pam_sm_setcred(
pamh: *mut PamHandle,
flags: PamFlag,
argc: c_int,
argv: *const *const c_char,
) -> PamResultCode {
unsafe {
$crate::macros::invoke_hook(pamh, argc, argv, |pamh, args| {
super::$ident::sm_setcred(pamh, args, flags)
})
}
}
}
};
}
#[macro_export]
macro_rules! pam_try {
($r:expr) => {
match $r {
Ok(t) => t,
Err(e) => return e,
}
};
($r:expr, $e:expr) => {
match $r {
Ok(t) => t,
Err(_) => return $e,
}
};
}
fn panic_guard<F: FnOnce() -> PamResultCode>(f: F) -> PamResultCode {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or(PamResultCode::PAM_ABORT)
}
unsafe fn extract_argv(
argv: &[*const std::os::raw::c_char],
) -> Result<Vec<&std::ffi::CStr>, PamResultCode> {
argv.iter()
.map(|&p| {
if p.is_null() {
Err(PamResultCode::PAM_ABORT)
} else {
Ok(unsafe { std::ffi::CStr::from_ptr(p) })
}
})
.collect()
}
#[doc(hidden)]
#[allow(clippy::similar_names)]
pub unsafe fn invoke_hook(
pamh: *mut crate::module::PamHandle,
argc: std::os::raw::c_int,
argv: *const *const std::os::raw::c_char,
hook: impl FnOnce(&mut crate::module::PamHandle, Vec<&std::ffi::CStr>) -> PamResultCode,
) -> PamResultCode {
panic_guard(|| {
let Some(pamh) = (unsafe { pamh.as_mut() }) else {
return PamResultCode::PAM_ABORT;
};
if argc < 0 || (argc > 0 && argv.is_null()) {
return PamResultCode::PAM_ABORT;
}
#[allow(clippy::cast_sign_loss)]
let argv: &[*const std::os::raw::c_char] = if argc == 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(argv, argc as usize) }
};
match unsafe { extract_argv(argv) } {
Ok(args) => hook(pamh, args),
Err(e) => e,
}
})
}
#[cfg(test)]
#[allow(clippy::panic, clippy::unwrap_used, clippy::needless_pass_by_value)]
pub mod test {
use crate::constants::PamResultCode;
use crate::module::{PamHandle, PamHooks};
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use std::ptr;
struct Foo;
impl PamHooks for Foo {}
pam_hooks!(Foo);
#[test]
fn panic_returns_error_code() {
let code = super::panic_guard(|| panic!("intentional"));
assert_eq!(code, PamResultCode::PAM_ABORT);
}
fn hook_success(_pamh: &mut PamHandle, _args: Vec<&CStr>) -> PamResultCode {
PamResultCode::PAM_SUCCESS
}
fn hook_panic(_pamh: &mut PamHandle, _args: Vec<&CStr>) -> PamResultCode {
panic!("hook panicked");
}
#[test]
fn invoke_hook_validation() {
struct Case {
pamh: *mut PamHandle,
argc: c_int,
argv: *const *const c_char,
hook: fn(&mut PamHandle, Vec<&CStr>) -> PamResultCode,
expected: PamResultCode,
}
let pamh = ptr::NonNull::<PamHandle>::dangling().as_ptr();
let one_null: [*const c_char; 1] = [ptr::null()];
let two_valid: [*const c_char; 2] = [c"first".as_ptr(), c"second".as_ptr()];
let cases = [
Case {
pamh: ptr::null_mut(),
argc: 0,
argv: ptr::null(),
hook: hook_success,
expected: PamResultCode::PAM_ABORT,
},
Case {
pamh,
argc: -1,
argv: ptr::null(),
hook: hook_success,
expected: PamResultCode::PAM_ABORT,
},
Case {
pamh,
argc: 1,
argv: ptr::null(),
hook: hook_success,
expected: PamResultCode::PAM_ABORT,
},
Case {
pamh,
argc: 1,
argv: one_null.as_ptr(),
hook: hook_success,
expected: PamResultCode::PAM_ABORT,
},
Case {
pamh,
argc: 0,
argv: ptr::null(),
hook: hook_panic,
expected: PamResultCode::PAM_ABORT,
},
Case {
pamh,
argc: 2,
argv: two_valid.as_ptr(),
hook: hook_success,
expected: PamResultCode::PAM_SUCCESS,
},
];
for case in cases {
let actual = unsafe { super::invoke_hook(case.pamh, case.argc, case.argv, case.hook) };
assert_eq!(case.expected, actual);
}
}
#[test]
fn test_extract_argv() {
let with_null: [*const c_char; 2] = [c"first".as_ptr(), ptr::null()];
assert_eq!(
unsafe { super::extract_argv(&with_null) }.unwrap_err(),
PamResultCode::PAM_ABORT
);
let empty: [*const c_char; 0] = [];
assert!(unsafe { super::extract_argv(&empty) }.unwrap().is_empty());
let valid: [*const c_char; 2] = [c"first".as_ptr(), c"second".as_ptr()];
let args = unsafe { super::extract_argv(&valid) }.unwrap();
assert_eq!(args.len(), 2);
assert_eq!(args[0].to_str().unwrap(), "first");
assert_eq!(args[1].to_str().unwrap(), "second");
}
}