use crate::{enums::Stage, error::*};
use libc::c_char;
use milter_sys as sys;
use once_cell::unsync::OnceCell;
use std::{
convert::TryInto,
ffi::{CStr, CString},
mem,
ptr::{self, NonNull},
result,
};
#[derive(Debug)]
pub struct Context<T> {
pub api: ContextApi,
pub data: DataHandle<T>,
}
impl<T> Context<T> {
pub fn new(ptr: *mut sys::SMFICTX) -> Self {
assert!(!ptr.is_null());
Self {
api: ContextApi::new(ptr),
data: DataHandle::new(ptr),
}
}
}
#[derive(Debug)]
pub struct ContextApi {
context_ptr: NonNull<sys::SMFICTX>,
}
impl ContextApi {
fn new(ptr: *mut sys::SMFICTX) -> Self {
assert!(!ptr.is_null());
Self {
context_ptr: NonNull::new(ptr).unwrap(),
}
}
pub fn request_macros(&self, stage: Stage, macros: &str) -> Result<()> {
let macros = CString::new(macros)?;
let ret_code = unsafe {
sys::smfi_setsymlist(self.context_ptr.as_ptr(), stage as _, macros.as_ptr() as _)
};
ReturnCode::from(ret_code).into()
}
pub fn macro_value(&self, name: &str) -> Result<Option<&str>> {
let name = CString::new(name)?;
unsafe {
let value = sys::smfi_getsymval(self.context_ptr.as_ptr(), name.as_ptr() as _);
Ok(if value.is_null() {
None
} else {
Some(CStr::from_ptr(value).to_str()?)
})
}
}
pub fn set_error_reply(
&self,
code: &str,
ext_code: Option<&str>,
msg_lines: Vec<&str>,
) -> Result<()> {
let code = CString::new(code)?;
let ext_code = ext_code.map(CString::new).transpose()?;
let l: Vec<CString> = msg_lines
.into_iter()
.map(CString::new)
.collect::<result::Result<_, _>>()?;
let p = self.context_ptr.as_ptr();
let c = code.as_ptr() as *mut _;
let x = ext_code.as_ref().map_or(ptr::null_mut(), |s| s.as_ptr() as *mut _);
macro_rules! set_reply {
($line:expr) => {
sys::smfi_setreply(p, c, x, $line as _)
};
($($line:expr),+) => {
sys::smfi_setmlreply(
p, c, x, $($line.as_ptr() as *mut c_char),+, ptr::null_mut() as *mut c_char
)
}
}
let ret_code = unsafe {
match l.len() {
0 => set_reply!(ptr::null_mut()),
1 => set_reply!(l[0].as_ptr()),
2 => set_reply!(l[0], l[1]),
3 => set_reply!(l[0], l[1], l[2]),
4 => set_reply!(l[0], l[1], l[2], l[3]),
5 => set_reply!(l[0], l[1], l[2], l[3], l[4]),
6 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5]),
7 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6]),
8 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7]),
9 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8]),
10 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9]),
11 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10]),
12 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11]),
13 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12]),
14 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13]),
15 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14]),
16 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15]),
17 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], l[16]),
18 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], l[16], l[17]),
19 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], l[16], l[17], l[18]),
20 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], l[16], l[17], l[18], l[19]),
21 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], l[16], l[17], l[18], l[19], l[20]),
22 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], l[16], l[17], l[18], l[19], l[20], l[21]),
23 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], l[16], l[17], l[18], l[19], l[20], l[21], l[22]),
24 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], l[16], l[17], l[18], l[19], l[20], l[21], l[22], l[23]),
25 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], l[16], l[17], l[18], l[19], l[20], l[21], l[22], l[23], l[24]),
26 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], l[16], l[17], l[18], l[19], l[20], l[21], l[22], l[23], l[24], l[25]),
27 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], l[16], l[17], l[18], l[19], l[20], l[21], l[22], l[23], l[24], l[25], l[26]),
28 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], l[16], l[17], l[18], l[19], l[20], l[21], l[22], l[23], l[24], l[25], l[26], l[27]),
29 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], l[16], l[17], l[18], l[19], l[20], l[21], l[22], l[23], l[24], l[25], l[26], l[27], l[28]),
30 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], l[16], l[17], l[18], l[19], l[20], l[21], l[22], l[23], l[24], l[25], l[26], l[27], l[28], l[29]),
31 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], l[16], l[17], l[18], l[19], l[20], l[21], l[22], l[23], l[24], l[25], l[26], l[27], l[28], l[29], l[30]),
32 => set_reply!(l[0], l[1], l[2], l[3], l[4], l[5], l[6], l[7], l[8], l[9], l[10], l[11], l[12], l[13], l[14], l[15], l[16], l[17], l[18], l[19], l[20], l[21], l[22], l[23], l[24], l[25], l[26], l[27], l[28], l[29], l[30], l[31]),
_ => panic!("more than 32 message lines"),
}
};
ReturnCode::from(ret_code).into()
}
pub fn replace_sender(&self, mail_from: &str, esmtp_args: Option<&str>) -> Result<()> {
let mail_from = CString::new(mail_from)?;
let esmtp_args = esmtp_args.map(CString::new).transpose()?;
let ret_code = unsafe {
sys::smfi_chgfrom(
self.context_ptr.as_ptr(),
mail_from.as_ptr() as _,
esmtp_args.as_ref().map_or(ptr::null_mut(), |s| s.as_ptr() as _),
)
};
ReturnCode::from(ret_code).into()
}
pub fn add_recipient(&self, rcpt_to: &str, esmtp_args: Option<&str>) -> Result<()> {
let rcpt_to = CString::new(rcpt_to)?;
let ret_code = unsafe {
match esmtp_args {
None => sys::smfi_addrcpt(self.context_ptr.as_ptr(), rcpt_to.as_ptr() as _),
Some(esmtp_args) => {
let esmtp_args = CString::new(esmtp_args)?;
sys::smfi_addrcpt_par(
self.context_ptr.as_ptr(),
rcpt_to.as_ptr() as _,
esmtp_args.as_ptr() as _,
)
}
}
};
ReturnCode::from(ret_code).into()
}
pub fn remove_recipient(&self, rcpt_to: &str) -> Result<()> {
let rcpt_to = CString::new(rcpt_to)?;
let ret_code = unsafe {
sys::smfi_delrcpt(self.context_ptr.as_ptr(), rcpt_to.as_ptr() as _)
};
ReturnCode::from(ret_code).into()
}
pub fn add_header(&self, name: &str, value: &str) -> Result<()> {
let name = CString::new(name)?;
let value = CString::new(value)?;
let ret_code = unsafe {
sys::smfi_addheader(self.context_ptr.as_ptr(), name.as_ptr() as _, value.as_ptr() as _)
};
ReturnCode::from(ret_code).into()
}
pub fn insert_header(&self, index: usize, name: &str, value: &str) -> Result<()> {
let index = index.try_into()?;
let name = CString::new(name)?;
let value = CString::new(value)?;
let ret_code = unsafe {
sys::smfi_insheader(
self.context_ptr.as_ptr(),
index,
name.as_ptr() as _,
value.as_ptr() as _,
)
};
ReturnCode::from(ret_code).into()
}
pub fn replace_header(&self, name: &str, index: usize, value: Option<&str>) -> Result<()> {
let name = CString::new(name)?;
let index = index.try_into()?;
let value = value.map(CString::new).transpose()?;
let ret_code = unsafe {
sys::smfi_chgheader(
self.context_ptr.as_ptr(),
name.as_ptr() as _,
index,
value.as_ref().map_or(ptr::null_mut(), |s| s.as_ptr() as _),
)
};
ReturnCode::from(ret_code).into()
}
pub fn append_body_chunk(&self, content: &[u8]) -> Result<()> {
let ret_code = unsafe {
sys::smfi_replacebody(
self.context_ptr.as_ptr(),
content.as_ptr() as _,
content.len() as _,
)
};
ReturnCode::from(ret_code).into()
}
pub fn quarantine(&self, reason: &str) -> Result<()> {
let reason = CString::new(reason)?;
let ret_code = unsafe {
sys::smfi_quarantine(self.context_ptr.as_ptr(), reason.as_ptr() as _)
};
ReturnCode::from(ret_code).into()
}
pub fn signal_progress(&self) -> Result<()> {
let ret_code = unsafe { sys::smfi_progress(self.context_ptr.as_ptr()) };
ReturnCode::from(ret_code).into()
}
}
pub trait MacroValue {
fn macro_value(&self, name: &str) -> Result<Option<&str>>;
}
impl MacroValue for ContextApi {
fn macro_value(&self, name: &str) -> Result<Option<&str>> {
Self::macro_value(self, name)
}
}
pub trait SetErrorReply {
fn set_error_reply(&self, code: &str, ext_code: Option<&str>, msg_lines: Vec<&str>) -> Result<()>;
}
impl SetErrorReply for ContextApi {
fn set_error_reply(&self, code: &str, ext_code: Option<&str>, msg_lines: Vec<&str>) -> Result<()> {
Self::set_error_reply(self, code, ext_code, msg_lines)
}
}
pub trait ActionContext {
fn replace_sender(&self, mail_from: &str, esmtp_args: Option<&str>) -> Result<()>;
fn add_recipient(&self, rcpt_to: &str, esmtp_args: Option<&str>) -> Result<()>;
fn remove_recipient(&self, rcpt_to: &str) -> Result<()>;
fn add_header(&self, name: &str, value: &str) -> Result<()>;
fn insert_header(&self, index: usize, name: &str, value: &str) -> Result<()>;
fn replace_header(&self, name: &str, index: usize, value: Option<&str>) -> Result<()>;
fn append_body_chunk(&self, content: &[u8]) -> Result<()>;
fn quarantine(&self, reason: &str) -> Result<()>;
fn signal_progress(&self) -> Result<()>;
}
impl ActionContext for ContextApi {
fn replace_sender(&self, mail_from: &str, esmtp_args: Option<&str>) -> Result<()> {
Self::replace_sender(self, mail_from, esmtp_args)
}
fn add_recipient(&self, rcpt_to: &str, esmtp_args: Option<&str>) -> Result<()> {
Self::add_recipient(self, rcpt_to, esmtp_args)
}
fn remove_recipient(&self, rcpt_to: &str) -> Result<()> {
Self::remove_recipient(self, rcpt_to)
}
fn add_header(&self, name: &str, value: &str) -> Result<()> {
Self::add_header(self, name, value)
}
fn insert_header(&self, index: usize, name: &str, value: &str) -> Result<()> {
Self::insert_header(self, index, name, value)
}
fn replace_header(&self, name: &str, index: usize, value: Option<&str>) -> Result<()> {
Self::replace_header(self, name, index, value)
}
fn append_body_chunk(&self, content: &[u8]) -> Result<()> {
Self::append_body_chunk(self, content)
}
fn quarantine(&self, reason: &str) -> Result<()> {
Self::quarantine(self, reason)
}
fn signal_progress(&self) -> Result<()> {
Self::signal_progress(self)
}
}
#[derive(Debug)]
pub struct DataHandle<T> {
context_ptr: NonNull<sys::SMFICTX>,
data_ptr: OnceCell<Option<NonNull<T>>>,
}
impl<T> DataHandle<T> {
fn new(ptr: *mut sys::SMFICTX) -> Self {
assert!(!ptr.is_null());
Self {
context_ptr: NonNull::new(ptr).unwrap(),
data_ptr: OnceCell::new(),
}
}
pub fn replace(&mut self, data: T) -> Result<Option<T>> {
unsafe { self.replace_data(Box::into_raw(Box::new(data))) }
}
pub fn take(&mut self) -> Result<Option<T>> {
unsafe { self.replace_data(ptr::null_mut()) }
}
unsafe fn replace_data(&mut self, ptr: *mut T) -> Result<Option<T>> {
self.get_or_init_data();
let ret_code = sys::smfi_setpriv(self.context_ptr.as_ptr(), ptr as _).into();
match ret_code {
ReturnCode::Success => {
Ok(mem::replace(self.data_ptr.get_mut().unwrap(), NonNull::new(ptr))
.map(|x| *Box::from_raw(x.as_ptr())))
}
ReturnCode::Failure => {
if !ptr.is_null() {
Box::from_raw(ptr);
}
Err(Error::FailureStatus)
}
}
}
pub fn borrow(&self) -> Option<&T> {
self.get_or_init_data().as_ref().map(|x| unsafe { x.as_ref() })
}
pub fn borrow_mut(&mut self) -> Option<&mut T> {
self.get_or_init_data();
self.data_ptr.get_mut().unwrap().as_mut().map(|x| unsafe { x.as_mut() })
}
fn get_or_init_data(&self) -> &Option<NonNull<T>> {
self.data_ptr.get_or_init(|| {
NonNull::new(unsafe { sys::smfi_getpriv(self.context_ptr.as_ptr()) as _ })
})
}
}