use crate::error::*;
use crate::Stage;
use libc::c_char;
use milter_sys as sys;
use std::cell::{Ref, RefCell, RefMut};
use std::convert::TryInto;
use std::ffi::{CStr, CString};
use std::ptr::{self, NonNull};
use std::result;
pub struct Context<T> {
base: ContextBase,
pub data: DataHandle<T>,
}
impl<T> Context<T> {
pub fn new(ptr: *mut sys::SMFICTX) -> Self {
assert!(!ptr.is_null());
Context {
base: ContextBase::new(ptr),
data: DataHandle::new(ptr),
}
}
pub fn macro_value(&self, name: &str) -> Result<Option<&str>> {
self.base.macro_value(name)
}
pub fn set_requested_macros(&self, stage: Stage, macros: &str) -> Result<()> {
self.base.set_requested_macros(stage, macros)
}
pub fn set_error_reply(&self, code: &str, ext_code: Option<&str>, msg_lines: Vec<&str>) -> Result<()> {
self.base.set_error_reply(code, ext_code, msg_lines)
}
pub fn shut_down(&self) {
self.base.shut_down()
}
}
pub struct ActionContext<T = ()> {
base: ContextBase,
pub data: DataHandle<T>,
}
impl<T> ActionContext<T> {
pub fn new(ptr: *mut sys::SMFICTX) -> Self {
assert!(!ptr.is_null());
ActionContext {
base: ContextBase::new(ptr),
data: DataHandle::new(ptr),
}
}
pub fn macro_value(&self, name: &str) -> Result<Option<&str>> {
self.base.macro_value(name)
}
pub fn set_error_reply(&self, code: &str, ext_code: Option<&str>, msg_lines: Vec<&str>) -> Result<()> {
self.base.set_error_reply(code, ext_code, msg_lines)
}
pub fn shut_down(&self) {
self.base.shut_down()
}
pub fn change_sender(&self, mail_from: &str, esmtp_args: Option<&str>) -> Result<()> {
self.base.change_sender(mail_from, esmtp_args)
}
pub fn add_recipient(&self, rcpt_to: &str, esmtp_args: Option<&str>) -> Result<()> {
self.base.add_recipient(rcpt_to, esmtp_args)
}
pub fn delete_recipient(&self, rcpt_to: &str) -> Result<()> {
self.base.delete_recipient(rcpt_to)
}
pub fn add_header(&self, name: &str, value: &str) -> Result<()> {
self.base.add_header(name, value)
}
pub fn insert_header(&self, index: usize, name: &str, value: &str) -> Result<()> {
self.base.insert_header(index, name, value)
}
pub fn change_header(&self, name: &str, index: usize, value: Option<&str>) -> Result<()> {
self.base.change_header(name, index, value)
}
pub fn append_new_body(&self, content: &[u8]) -> Result<()> {
self.base.append_new_body(content)
}
pub fn quarantine(&self, reason: &str) -> Result<()> {
self.base.quarantine(reason)
}
pub fn keepalive(&self) -> Result<()> {
self.base.keepalive()
}
}
struct ContextBase {
context_ptr: NonNull<sys::SMFICTX>,
}
impl ContextBase {
fn new(ptr: *mut sys::SMFICTX) -> Self {
ContextBase { context_ptr: NonNull::new(ptr).unwrap() }
}
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()?)
})
}
}
fn set_requested_macros(&self, stage: Stage, macros: &str) -> Result<()> {
let macros = CString::new(macros)?;
let status_code = unsafe {
sys::smfi_setsymlist(self.context_ptr.as_ptr(), stage as _, macros.as_ptr() as _)
};
StatusCode::from(status_code).into()
}
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 status_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"),
}
};
StatusCode::from(status_code).into()
}
fn shut_down(&self) {
let _ = unsafe { sys::smfi_stop() };
}
fn change_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 status_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 _),
)
};
StatusCode::from(status_code).into()
}
fn add_recipient(&self, rcpt_to: &str, esmtp_args: Option<&str>) -> Result<()> {
let rcpt_to = CString::new(rcpt_to)?;
let status_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 _,
)
}
}
};
StatusCode::from(status_code).into()
}
fn delete_recipient(&self, rcpt_to: &str) -> Result<()> {
let rcpt_to = CString::new(rcpt_to)?;
let status_code = unsafe {
sys::smfi_delrcpt(self.context_ptr.as_ptr(), rcpt_to.as_ptr() as _)
};
StatusCode::from(status_code).into()
}
fn add_header(&self, name: &str, value: &str) -> Result<()> {
let name = CString::new(name)?;
let value = CString::new(value)?;
let status_code = unsafe {
sys::smfi_addheader(self.context_ptr.as_ptr(), name.as_ptr() as _, value.as_ptr() as _)
};
StatusCode::from(status_code).into()
}
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 status_code = unsafe {
sys::smfi_insheader(
self.context_ptr.as_ptr(),
index,
name.as_ptr() as _,
value.as_ptr() as _,
)
};
StatusCode::from(status_code).into()
}
fn change_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 status_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 _),
)
};
StatusCode::from(status_code).into()
}
fn append_new_body(&self, content: &[u8]) -> Result<()> {
let status_code = unsafe {
sys::smfi_replacebody(
self.context_ptr.as_ptr(),
content.as_ptr() as _,
content.len() as _,
)
};
StatusCode::from(status_code).into()
}
fn quarantine(&self, reason: &str) -> Result<()> {
let reason = CString::new(reason)?;
let status_code = unsafe {
sys::smfi_quarantine(self.context_ptr.as_ptr(), reason.as_ptr() as _)
};
StatusCode::from(status_code).into()
}
fn keepalive(&self) -> Result<()> {
let status_code = unsafe {
sys::smfi_progress(self.context_ptr.as_ptr())
};
StatusCode::from(status_code).into()
}
}
pub struct DataHandle<T> {
context_ptr: NonNull<sys::SMFICTX>,
data_ptr: RefCell<Option<NonNull<T>>>,
}
impl<T> DataHandle<T> {
fn new(ptr: *mut sys::SMFICTX) -> Self {
assert!(!ptr.is_null());
let data_ptr = unsafe { sys::smfi_getpriv(ptr) as _ };
DataHandle {
context_ptr: NonNull::new(ptr).unwrap(),
data_ptr: RefCell::new(NonNull::new(data_ptr)),
}
}
pub fn replace(&self, data: T) -> Result<Option<T>> {
unsafe { self.replace_data(Box::into_raw(Box::new(data))) }
}
pub fn take(&self) -> Result<Option<T>> {
unsafe { self.replace_data(ptr::null_mut()) }
}
unsafe fn replace_data(&self, ptr: *mut T) -> Result<Option<T>> {
let status_code = sys::smfi_setpriv(self.context_ptr.as_ptr(), ptr as _).into();
match status_code {
StatusCode::Success => {
Ok(self
.data_ptr
.replace(NonNull::new(ptr))
.map(|x| *Box::from_raw(x.as_ptr())))
}
StatusCode::Failure => {
if !ptr.is_null() {
Box::from_raw(ptr);
}
Err(Error::from(ErrorKind::MilterFailureStatus))
}
}
}
pub fn borrow(&self) -> Result<Option<Ref<T>>> {
let data_ref = self.data_ptr.try_borrow()
.map_err(|e| Error::new(ErrorKind::DataAccessError, e))?;
Ok(if data_ref.is_none() {
None
} else {
Some(Ref::map(data_ref, |x| unsafe { x.as_ref().unwrap().as_ref() }))
})
}
pub fn borrow_mut(&self) -> Result<Option<RefMut<T>>> {
let data_ref = self.data_ptr.try_borrow_mut()
.map_err(|e| Error::new(ErrorKind::DataAccessError, e))?;
Ok(if data_ref.is_none() {
None
} else {
Some(RefMut::map(data_ref, |x| unsafe { x.as_mut().unwrap().as_mut() }))
})
}
}