use crate::{enums::Actions, error::*};
use libc::{c_char, c_uchar, c_ulong, size_t};
use milter_sys as sys;
use std::{convert::TryInto, ffi::CString, ptr, time::Duration};
pub type NegotiateCallback = unsafe extern "C" fn(
*mut sys::SMFICTX, c_ulong, c_ulong, c_ulong, c_ulong, *mut c_ulong, *mut c_ulong, *mut c_ulong, *mut c_ulong
) -> sys::sfsistat;
pub type ConnectCallback = unsafe extern "C" fn(*mut sys::SMFICTX, *mut c_char, *mut libc::sockaddr) -> sys::sfsistat;
pub type HeloCallback = unsafe extern "C" fn(*mut sys::SMFICTX, *mut c_char) -> sys::sfsistat;
pub type MailCallback = unsafe extern "C" fn(*mut sys::SMFICTX, *mut *mut c_char) -> sys::sfsistat;
pub type RcptCallback = unsafe extern "C" fn(*mut sys::SMFICTX, *mut *mut c_char) -> sys::sfsistat;
pub type DataCallback = unsafe extern "C" fn(*mut sys::SMFICTX) -> sys::sfsistat;
pub type HeaderCallback = unsafe extern "C" fn(*mut sys::SMFICTX, *mut c_char, *mut c_char) -> sys::sfsistat;
pub type EohCallback = unsafe extern "C" fn(*mut sys::SMFICTX) -> sys::sfsistat;
pub type BodyCallback = unsafe extern "C" fn(*mut sys::SMFICTX, *mut c_uchar, size_t) -> sys::sfsistat;
pub type EomCallback = unsafe extern "C" fn(*mut sys::SMFICTX) -> sys::sfsistat;
pub type AbortCallback = unsafe extern "C" fn(*mut sys::SMFICTX) -> sys::sfsistat;
pub type CloseCallback = unsafe extern "C" fn(*mut sys::SMFICTX) -> sys::sfsistat;
pub type UnknownCallback = unsafe extern "C" fn(*mut sys::SMFICTX, *const c_char) -> sys::sfsistat;
#[derive(Clone, Debug)]
pub struct Milter {
socket: CString,
name: Option<CString>,
negotiate_callback: Option<NegotiateCallback>,
connect_callback: Option<ConnectCallback>,
helo_callback: Option<HeloCallback>,
mail_callback: Option<MailCallback>,
rcpt_callback: Option<RcptCallback>,
data_callback: Option<DataCallback>,
header_callback: Option<HeaderCallback>,
eoh_callback: Option<EohCallback>,
body_callback: Option<BodyCallback>,
eom_callback: Option<EomCallback>,
abort_callback: Option<AbortCallback>,
close_callback: Option<CloseCallback>,
unknown_callback: Option<UnknownCallback>,
actions: Actions,
timeout: Option<i32>,
socket_backlog: Option<i32>,
remove_socket: bool,
}
impl Milter {
pub fn new(socket: &str) -> Self {
let socket = CString::new(socket).expect("zero byte in socket specification");
Self {
socket,
name: None,
negotiate_callback: None,
connect_callback: None,
helo_callback: None,
mail_callback: None,
rcpt_callback: None,
data_callback: None,
header_callback: None,
eoh_callback: None,
body_callback: None,
eom_callback: None,
abort_callback: None,
close_callback: None,
unknown_callback: None,
actions: Default::default(),
timeout: None,
socket_backlog: None,
remove_socket: false,
}
}
pub fn name(&mut self, name: &str) -> &mut Self {
self.name = Some(CString::new(name).expect("zero byte in milter name"));
self
}
pub fn on_negotiate(&mut self, callback: NegotiateCallback) -> &mut Self {
self.negotiate_callback = Some(callback);
self
}
pub fn on_connect(&mut self, callback: ConnectCallback) -> &mut Self {
self.connect_callback = Some(callback);
self
}
pub fn on_helo(&mut self, callback: HeloCallback) -> &mut Self {
self.helo_callback = Some(callback);
self
}
pub fn on_mail(&mut self, callback: MailCallback) -> &mut Self {
self.mail_callback = Some(callback);
self
}
pub fn on_rcpt(&mut self, callback: RcptCallback) -> &mut Self {
self.rcpt_callback = Some(callback);
self
}
pub fn on_data(&mut self, callback: DataCallback) -> &mut Self {
self.data_callback = Some(callback);
self
}
pub fn on_header(&mut self, callback: HeaderCallback) -> &mut Self {
self.header_callback = Some(callback);
self
}
pub fn on_eoh(&mut self, callback: EohCallback) -> &mut Self {
self.eoh_callback = Some(callback);
self
}
pub fn on_body(&mut self, callback: BodyCallback) -> &mut Self {
self.body_callback = Some(callback);
self
}
pub fn on_eom(&mut self, callback: EomCallback) -> &mut Self {
self.eom_callback = Some(callback);
self
}
pub fn on_abort(&mut self, callback: AbortCallback) -> &mut Self {
self.abort_callback = Some(callback);
self
}
pub fn on_close(&mut self, callback: CloseCallback) -> &mut Self {
self.close_callback = Some(callback);
self
}
pub fn on_unknown(&mut self, callback: UnknownCallback) -> &mut Self {
self.unknown_callback = Some(callback);
self
}
pub fn actions(&mut self, actions: Actions) -> &mut Self {
self.actions = actions;
self
}
pub fn timeout(&mut self, duration: Duration) -> &mut Self {
self.timeout = Some(duration.as_secs().try_into().expect("timeout duration out of range"));
self
}
pub fn socket_backlog(&mut self, len: u32) -> &mut Self {
self.socket_backlog = Some(len.try_into().expect("socket backlog length out of range"));
self
}
pub fn remove_socket(&mut self, value: bool) -> &mut Self {
self.remove_socket = value;
self
}
pub fn run(&self) -> Result<()> {
unsafe {
self.register()?;
crate::internal::set_panicked(false);
match sys::smfi_main().into() {
ReturnCode::Success => {
if crate::internal::is_panicked() {
Err(Error::CallbackPanic)
} else {
Ok(())
}
}
ReturnCode::Failure => Err(Error::Main),
}
}
}
unsafe fn register(&self) -> Result<()> {
if let ReturnCode::Failure = sys::smfi_setconn(self.socket.as_ptr() as _).into() {
return Err(Error::SocketConfig(self.socket.clone()));
}
let descriptor = sys::smfiDesc {
xxfi_name: self.name.as_ref().map_or(ptr::null_mut(), |s| s.as_ptr() as _),
xxfi_version: sys::SMFI_VERSION,
xxfi_flags: self.actions.bits(),
xxfi_connect: self.connect_callback,
xxfi_helo: self.helo_callback,
xxfi_envfrom: self.mail_callback,
xxfi_envrcpt: self.rcpt_callback,
xxfi_header: self.header_callback,
xxfi_eoh: self.eoh_callback,
xxfi_body: self.body_callback,
xxfi_eom: self.eom_callback,
xxfi_abort: self.abort_callback,
xxfi_close: self.close_callback,
xxfi_unknown: self.unknown_callback,
xxfi_data: self.data_callback,
xxfi_negotiate: self.negotiate_callback,
};
if let ReturnCode::Failure = sys::smfi_register(descriptor).into() {
return Err(Error::Registration);
}
if let ReturnCode::Failure = sys::smfi_opensocket(self.remove_socket as _).into() {
return Err(Error::SocketConfig(self.socket.clone()));
}
if let Some(timeout) = self.timeout {
if let ReturnCode::Failure = sys::smfi_settimeout(timeout).into() {
return Err(Error::TimeoutConfig(timeout));
}
}
if let Some(socket_backlog) = self.socket_backlog {
if let ReturnCode::Failure = sys::smfi_setbacklog(socket_backlog).into() {
return Err(Error::SocketBacklogConfig(socket_backlog));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_milter() {
let mut milter = Milter::new("unix:/run/miltertest.sock");
milter
.name("test")
.timeout(Duration::from_secs(30))
.socket_backlog(64);
}
}