use libc::{c_char, c_int};
use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use crate::constants::{PamFlag, PamResultCode};
#[repr(C)]
pub struct PamHandle {
_data: [u8; 0],
_marker: PhantomData<*const ()>,
}
#[link(name = "pam")]
unsafe extern "C" {
fn pam_get_data(
pamh: *const PamHandle,
module_data_name: *const c_char,
data: &mut *const libc::c_void,
) -> c_int;
fn pam_set_data(
pamh: *mut PamHandle,
module_data_name: *const c_char,
data: *mut libc::c_void,
cleanup: extern "C" fn(pamh: *mut PamHandle, data: *mut libc::c_void, error_status: c_int),
) -> c_int;
fn pam_get_item(
pamh: *const PamHandle,
item_type: c_int,
item: &mut *const libc::c_void,
) -> c_int;
fn pam_set_item(pamh: *mut PamHandle, item_type: c_int, item: *const libc::c_void) -> c_int;
fn pam_get_user(pamh: *mut PamHandle, user: &mut *const c_char, prompt: *const c_char)
-> c_int;
}
extern "C" fn cleanup<T>(_: *mut PamHandle, c_data: *mut libc::c_void, _: c_int) {
if c_data.is_null() {
return;
}
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
let _data: Box<T> = Box::from_raw(c_data.cast::<T>());
}));
if let Err(payload) = result {
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| drop(payload)))
.map_err(std::mem::forget);
}
}
pub type PamResult<T> = Result<T, PamResultCode>;
impl PamHandle {
pub unsafe fn get_data<'a, T>(&'a self, key: &str) -> PamResult<&'a T> {
let c_key = CString::new(key).map_err(|_| PamResultCode::PAM_BUF_ERR)?;
let mut ptr: *const libc::c_void = std::ptr::null();
let res = PamResultCode::from_raw(unsafe { pam_get_data(self, c_key.as_ptr(), &mut ptr) });
if PamResultCode::PAM_SUCCESS != res {
return Err(res);
}
if ptr.is_null() {
return Err(PamResultCode::PAM_SYSTEM_ERR);
}
let typed_ptr = ptr.cast::<T>();
let data: &T = unsafe { &*typed_ptr };
Ok(data)
}
pub fn set_data<T: 'static>(&mut self, key: &str, data: Box<T>) -> PamResult<()> {
let c_key = CString::new(key).map_err(|_| PamResultCode::PAM_BUF_ERR)?;
let ptr = Box::into_raw(data);
let res = PamResultCode::from_raw(unsafe {
pam_set_data(
self,
c_key.as_ptr(),
ptr.cast::<libc::c_void>(),
cleanup::<T>,
)
});
if PamResultCode::PAM_SUCCESS == res {
Ok(())
} else {
drop(unsafe { Box::from_raw(ptr) });
Err(res)
}
}
pub fn get_item<'a, T: crate::items::Item<'a>>(&'a self) -> PamResult<Option<T>> {
let mut ptr: *const libc::c_void = std::ptr::null();
let res =
PamResultCode::from_raw(unsafe { pam_get_item(self, T::type_id() as c_int, &mut ptr) });
if PamResultCode::PAM_SUCCESS != res {
return Err(res);
}
let typed_ptr = ptr.cast::<T::Raw>();
if typed_ptr.is_null() {
Ok(None)
} else {
Ok(Some(unsafe { T::from_raw(typed_ptr) }))
}
}
pub fn set_item_str<'a, T: crate::items::Item<'a>>(&mut self, item: T) -> PamResult<()> {
let res = PamResultCode::from_raw(unsafe {
pam_set_item(
self,
T::type_id() as c_int,
item.into_raw().cast::<libc::c_void>(),
)
});
if PamResultCode::PAM_SUCCESS == res {
Ok(())
} else {
Err(res)
}
}
pub fn get_user(&mut self, prompt: Option<&str>) -> PamResult<String> {
let mut ptr: *const c_char = std::ptr::null();
let prompt_string = prompt
.map(CString::new)
.transpose()
.map_err(|_| PamResultCode::PAM_BUF_ERR)?;
let c_prompt = prompt_string
.as_ref()
.map_or(std::ptr::null(), |s| s.as_ptr());
let res = PamResultCode::from_raw(unsafe { pam_get_user(self, &mut ptr, c_prompt) });
if PamResultCode::PAM_SUCCESS != res {
return Err(res);
}
if ptr.is_null() {
return Err(PamResultCode::PAM_SYSTEM_ERR);
}
let bytes = unsafe { CStr::from_ptr(ptr).to_bytes() };
String::from_utf8(bytes.to_vec()).map_err(|_| PamResultCode::PAM_SYSTEM_ERR)
}
}
#[allow(unused_variables)]
pub trait PamHooks {
fn acct_mgmt(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
PamResultCode::PAM_IGNORE
}
fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
PamResultCode::PAM_IGNORE
}
fn sm_chauthtok(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
PamResultCode::PAM_IGNORE
}
fn sm_close_session(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
PamResultCode::PAM_IGNORE
}
fn sm_open_session(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
PamResultCode::PAM_IGNORE
}
fn sm_setcred(pamh: &mut PamHandle, args: Vec<&CStr>, flags: PamFlag) -> PamResultCode {
PamResultCode::PAM_IGNORE
}
}
#[cfg(test)]
#[allow(clippy::panic)]
mod tests {
use super::*;
#[test]
fn cleanup_disaster_scenarios() {
cleanup::<String>(std::ptr::null_mut(), std::ptr::null_mut(), 0);
{
struct Bomb;
impl Drop for Bomb {
fn drop(&mut self) {
panic!();
}
}
let ptr = Box::into_raw(Box::new(Bomb)).cast::<libc::c_void>();
cleanup::<Bomb>(std::ptr::null_mut(), ptr, 0);
}
{
struct BombRecursive;
impl Drop for BombRecursive {
fn drop(&mut self) {
std::panic::panic_any(Self);
}
}
let ptr = Box::into_raw(Box::new(BombRecursive)).cast::<libc::c_void>();
cleanup::<BombRecursive>(std::ptr::null_mut(), ptr, 0);
}
}
}