use libc::{c_char, c_int};
use std::ffi::{CStr, CString};
use std::ptr;
use crate::constants::PamMessageStyle;
use crate::constants::PamResultCode;
use crate::constants::{
PAM_ERROR_MSG, PAM_PROMPT_ECHO_OFF, PAM_PROMPT_ECHO_ON, PAM_RADIO_TYPE, PAM_TEXT_INFO,
};
use crate::items::Item;
use crate::module::PamResult;
use crate::secret::{SecretBytes, zeroize_raw};
#[repr(C)]
struct PamMessage {
msg_style: PamMessageStyle,
msg: *const c_char,
}
#[repr(C)]
struct PamResponse {
resp: *mut c_char,
resp_retcode: libc::c_int, }
#[repr(C)]
pub struct Inner {
conv: Option<
extern "C" fn(
num_msg: c_int,
pam_message: *mut *const PamMessage,
pam_response: *mut *mut PamResponse,
appdata_ptr: *mut libc::c_void,
) -> c_int,
>,
appdata_ptr: *mut libc::c_void,
}
pub struct Conv<'a>(&'a Inner);
impl Conv<'_> {
pub fn send(&self, style: PamMessageStyle, msg: &str) -> PamResult<Option<SecretBytes>> {
match style {
PAM_PROMPT_ECHO_OFF | PAM_PROMPT_ECHO_ON | PAM_ERROR_MSG | PAM_TEXT_INFO
| PAM_RADIO_TYPE => {}
_ => return Err(PamResultCode::PAM_CONV_ERR),
}
let Some(conv_fn) = self.0.conv else {
return Err(PamResultCode::PAM_CONV_ERR);
};
let mut resp_ptr: *mut PamResponse = ptr::null_mut();
let msg_cstr = CString::new(msg).map_err(|_| PamResultCode::PAM_BUF_ERR)?;
let msg = PamMessage {
msg_style: style,
msg: msg_cstr.as_ptr(),
};
let mut msg_ptr: *const PamMessage = &raw const msg;
let ret = PamResultCode::from_raw(conv_fn(
1,
&raw mut msg_ptr,
&raw mut resp_ptr,
self.0.appdata_ptr,
));
if PamResultCode::PAM_SUCCESS != ret {
return Err(ret);
}
if resp_ptr.is_null() {
return Err(PamResultCode::PAM_CONV_ERR);
}
let resp_field = unsafe { (*resp_ptr).resp };
let response = if resp_field.is_null() {
None
} else {
let bytes = unsafe { CStr::from_ptr(resp_field) }.to_bytes().to_vec();
unsafe { zeroize_raw(resp_field.cast(), bytes.len()) };
unsafe { libc::free(resp_field.cast()) };
Some(SecretBytes::new(bytes))
};
unsafe { libc::free(resp_ptr.cast()) };
Ok(response)
}
}
impl<'a> Item<'a> for Conv<'a> {
type Raw = Inner;
fn type_id() -> crate::items::ItemType {
crate::items::ItemType::Conv
}
unsafe fn from_raw(raw: *const Self::Raw) -> Self {
unsafe { Self(&*raw) }
}
fn into_raw(self) -> *const Self::Raw {
std::ptr::from_ref(self.0)
}
}
#[cfg(test)]
#[allow(clippy::panic, clippy::unwrap_used)]
mod tests {
use super::*;
use crate::constants::PAM_BINARY_PROMPT;
extern "C" fn unreachable_conv(
_: c_int,
_: *mut *const PamMessage,
_: *mut *mut PamResponse,
_: *mut libc::c_void,
) -> c_int {
panic!("this conversation function shouldn't run");
}
struct SendTestCase {
inner: Inner,
style: PamMessageStyle,
expected: PamResult<Option<SecretBytes>>,
message: &'static str,
}
#[test]
fn send_error_cases() {
let test_cases: Vec<SendTestCase> = vec![
SendTestCase {
inner: Inner {
conv: Some(unreachable_conv),
appdata_ptr: ptr::null_mut(),
},
style: PAM_BINARY_PROMPT,
expected: Err(PamResultCode::PAM_CONV_ERR),
message: "",
},
SendTestCase {
inner: Inner {
conv: Some(unreachable_conv),
appdata_ptr: ptr::null_mut(),
},
style: c_int::MIN,
expected: Err(PamResultCode::PAM_CONV_ERR),
message: "",
},
SendTestCase {
inner: Inner {
conv: None,
appdata_ptr: ptr::null_mut(),
},
style: PAM_PROMPT_ECHO_OFF,
expected: Err(PamResultCode::PAM_CONV_ERR),
message: "",
},
SendTestCase {
inner: Inner {
conv: Some(unreachable_conv),
appdata_ptr: ptr::null_mut(),
},
style: PAM_PROMPT_ECHO_OFF,
expected: Err(PamResultCode::PAM_BUF_ERR),
message: "PAM is fun\0sometimes",
},
];
for test_case in test_cases {
let actual = Conv(&test_case.inner).send(test_case.style, test_case.message);
assert_eq!(test_case.expected.unwrap_err(), actual.unwrap_err());
}
}
}