use crate::libpam::conversation::OwnedExchange;
use crate::libpam::memory;
use crate::libpam::memory::{CHeapBox, CHeapPayload, CHeapString, Immovable};
use crate::{ErrorCode, Result};
use libpam_sys_helpers::BinaryPayload;
use std::ffi::{c_int, c_void, CStr, OsStr};
use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut};
use std::os::unix::ffi::OsStrExt;
use std::ptr::NonNull;
use std::{iter, ptr, slice};
#[derive(Debug)]
pub struct Answers {
base: NonNull<Answer>,
count: usize,
}
impl Answers {
pub fn build(value: Vec<OwnedExchange>) -> Result<Self> {
let mut outputs = Self {
base: memory::calloc(value.len()),
count: value.len(),
};
for (input, output) in iter::zip(value, outputs.iter_mut()) {
match input {
OwnedExchange::MaskedPrompt(p) => TextAnswer::fill(output, &p.answer()?)?,
OwnedExchange::Prompt(p) => TextAnswer::fill(output, &p.answer()?)?,
OwnedExchange::Error(p) => {
TextAnswer::fill(output, p.answer().map(|_| "".as_ref())?)?
}
OwnedExchange::Info(p) => {
TextAnswer::fill(output, p.answer().map(|_| "".as_ref())?)?
}
OwnedExchange::RadioPrompt(p) => TextAnswer::fill(output, &(p.answer()?))?,
OwnedExchange::BinaryPrompt(p) => {
BinaryAnswer::fill(output, (&p.answer()?).into())?
}
}
}
Ok(outputs)
}
pub fn into_ptr(self) -> *mut libpam_sys::pam_response {
ManuallyDrop::new(self).base.as_ptr().cast()
}
pub unsafe fn from_c_heap(base: NonNull<libpam_sys::pam_response>, count: usize) -> Self {
Answers {
base: NonNull::new_unchecked(base.as_ptr().cast()),
count,
}
}
}
impl Deref for Answers {
type Target = [Answer];
fn deref(&self) -> &Self::Target {
unsafe { slice::from_raw_parts(self.base.as_ptr(), self.count) }
}
}
impl DerefMut for Answers {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { slice::from_raw_parts_mut(self.base.as_ptr(), self.count) }
}
}
impl Drop for Answers {
fn drop(&mut self) {
unsafe {
for answer in self.iter_mut() {
ptr::drop_in_place(answer)
}
memory::free(self.base.as_ptr())
}
}
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct Answer {
pub data: Option<CHeapBox<c_void>>,
return_code: c_int,
_marker: Immovable,
}
#[repr(transparent)]
#[derive(Debug)]
pub struct TextAnswer(Answer);
impl TextAnswer {
pub unsafe fn upcast(from: &mut Answer) -> &mut Self {
&mut *(from as *mut Answer).cast::<Self>()
}
fn fill(dest: &mut Answer, text: &OsStr) -> Result<()> {
let allocated = CHeapString::new(text.as_bytes());
let _ = dest
.data
.replace(unsafe { CHeapBox::cast(allocated.into_box()) });
Ok(())
}
pub fn contents(&self) -> Result<&str> {
match self.0.data.as_ref() {
None => Ok(""),
Some(data) => {
unsafe { CStr::from_ptr(CHeapBox::as_ptr(data).as_ptr().cast()) }
.to_str()
.map_err(|_| ErrorCode::ConversationError)
}
}
}
pub fn zero_contents(&mut self) {
unsafe {
if let Some(ptr) = self.0.data.as_ref() {
CHeapString::zero(CHeapBox::as_ptr(ptr).cast());
}
}
}
}
#[repr(transparent)]
#[derive(Debug)]
pub struct BinaryAnswer(Answer);
impl BinaryAnswer {
pub unsafe fn upcast(from: &mut Answer) -> &mut Self {
&mut *(from as *mut Answer).cast::<Self>()
}
pub fn fill(dest: &mut Answer, (data, type_): (&[u8], u8)) -> Result<()> {
let payload = CHeapPayload::new(data, type_).map_err(|_| ErrorCode::BufferError)?;
let _ = dest
.data
.replace(unsafe { CHeapBox::cast(payload.into_inner()) });
Ok(())
}
pub fn contents(&self) -> Option<(&[u8], u8)> {
self.0
.data
.as_ref()
.map(|data| unsafe { BinaryPayload::contents(CHeapBox::as_ptr(data).cast().as_ptr()) })
}
pub fn zero_contents(&mut self) {
if let Some(data) = self.0.data.as_mut() {
unsafe {
let total = BinaryPayload::total_bytes(CHeapBox::as_ptr(data).cast().as_ref());
let data: &mut [u8] =
slice::from_raw_parts_mut(CHeapBox::as_raw_ptr(data).cast(), total);
data.fill(0)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::conv::{ErrorMsg, InfoMsg, MaskedQAndA, QAndA};
macro_rules! answered {
($typ:ty, $msg:path, $data:expr) => {{
let qa = <$typ>::new("".as_ref());
qa.set_answer(Ok($data));
$msg(qa)
}};
}
fn assert_text_answer(want: &str, answer: &mut Answer) {
let up = unsafe { TextAnswer::upcast(answer) };
assert_eq!(want, up.contents().unwrap());
up.zero_contents();
assert_eq!("", up.contents().unwrap());
}
fn round_trip(exchanges: Vec<OwnedExchange>) -> Answers {
let n = exchanges.len();
let sent = Answers::build(exchanges).unwrap();
unsafe { Answers::from_c_heap(NonNull::new_unchecked(sent.into_ptr()), n) }
}
#[test]
fn test_round_trip() {
let mut answers = round_trip(vec![
answered!(QAndA, OwnedExchange::Prompt, "whats going on".into()),
answered!(MaskedQAndA, OwnedExchange::MaskedPrompt, "well then".into()),
answered!(ErrorMsg, OwnedExchange::Error, ()),
answered!(InfoMsg, OwnedExchange::Info, ()),
]);
if let [going, well, err, info] = &mut answers[..] {
assert_text_answer("whats going on", going);
assert_text_answer("well then", well);
assert_text_answer("", err);
assert_text_answer("", info);
} else {
panic!("received wrong size {len}!", len = answers.len())
}
}
#[cfg(feature = "linux-pam-ext")]
fn test_round_trip_linux() {
use crate::conv::{BinaryData, BinaryQAndA, RadioQAndA};
let binary_msg = {
let qa = BinaryQAndA::new((&[][..], 0));
qa.set_answer(Ok(BinaryData::new(vec![1, 2, 3], 99)));
OwnedExchange::BinaryPrompt(qa)
};
let mut answers = round_trip(vec![
binary_msg,
answered!(RadioQAndA, OwnedExchange::RadioPrompt, "beep boop".into()),
]);
if let [bin, radio] = &mut answers[..] {
let up = unsafe { BinaryAnswer::upcast(bin) };
assert_eq!((&[1, 2, 3][..], 99), up.contents().unwrap());
up.zero_contents();
assert_eq!((&[][..], 0), up.contents().unwrap());
assert_text_answer("beep boop", radio);
} else {
panic!("received wrong size {len}!", len = answers.len())
}
}
#[test]
fn test_text_answer() {
let mut answer: CHeapBox<Answer> = CHeapBox::default();
TextAnswer::fill(&mut answer, "hello".as_ref()).unwrap();
let zeroth_text = unsafe { TextAnswer::upcast(&mut answer) };
let data = zeroth_text.contents().expect("valid");
assert_eq!("hello", data);
zeroth_text.zero_contents();
zeroth_text.zero_contents();
}
#[test]
#[should_panic]
fn test_text_answer_nul() {
TextAnswer::fill(&mut CHeapBox::default(), "hell\0".as_ref())
.expect_err("should error; contains nul");
}
#[test]
fn test_binary_answer() {
use crate::conv::BinaryData;
let mut answer: CHeapBox<Answer> = CHeapBox::default();
let real_data = BinaryData::new([1, 2, 3, 4, 5, 6, 7, 8], 9);
BinaryAnswer::fill(&mut answer, (&real_data).into()).expect("alloc should succeed");
let bin_answer = unsafe { BinaryAnswer::upcast(&mut answer) };
assert_eq!(real_data, bin_answer.contents().unwrap().into());
}
#[test]
#[ignore]
fn test_binary_answer_too_big() {
let big_data: Vec<u8> = vec![0xFFu8; 0x1_0000_0001];
let mut answer: CHeapBox<Answer> = CHeapBox::default();
BinaryAnswer::fill(&mut answer, (&big_data, 100)).expect_err("this is too big!");
}
}