Rewrite the project
The previous project architecture was far too complicated and hard to maintain. The new one is much more simple. Although procedural macros are cools, they are a no-go on Rust-OpenSMTPD. Reports and filter are implemented (except data-line) but untested.
This commit is contained in:
parent
fc072743ad
commit
a6d4dd21c1
48 changed files with 1723 additions and 1493 deletions
|
@ -1,298 +0,0 @@
|
|||
// Copyright (c) 2019 Rodolphe Bréard
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use crate::errors::Error;
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::streaming::{tag, take_till};
|
||||
use nom::character::streaming::{digit1, hex_digit1, line_ending};
|
||||
use nom::combinator::{map_res, value};
|
||||
use nom::multi::many0;
|
||||
use nom::Err::Incomplete;
|
||||
use nom::IResult;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub type SessionId = u64;
|
||||
pub type Token = u64;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Version {
|
||||
V1,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Kind {
|
||||
Report,
|
||||
Filter,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Subsystem {
|
||||
SmtpIn,
|
||||
SmtpOut,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum Event {
|
||||
LinkAuth,
|
||||
LinkConnect,
|
||||
LinkDisconnect,
|
||||
LinkIdentify,
|
||||
LinkReset,
|
||||
LinkTls,
|
||||
TxBegin,
|
||||
TxMail,
|
||||
TxRcpt,
|
||||
TxEnvelope,
|
||||
TxData,
|
||||
TxCommit,
|
||||
TxRollback,
|
||||
ProtocolClient,
|
||||
ProtocolServer,
|
||||
FilterResponse,
|
||||
Timeout,
|
||||
}
|
||||
|
||||
impl FromStr for Event {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.to_lowercase();
|
||||
let s = if !s.contains('-') {
|
||||
s.replace("link", "link-")
|
||||
.replace("tx", "tx-")
|
||||
.replace("protocol", "protocol-")
|
||||
.replace("filter", "filter-")
|
||||
} else {
|
||||
s
|
||||
};
|
||||
let (_, evt) = parse_event(&s)?;
|
||||
Ok(evt)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Event {
|
||||
fn to_string(&self) -> String {
|
||||
let s = match self {
|
||||
Event::LinkAuth => "link-auth",
|
||||
Event::LinkConnect => "link-connect",
|
||||
Event::LinkDisconnect => "link-disconnect",
|
||||
Event::LinkIdentify => "link-identify",
|
||||
Event::LinkReset => "link-reset",
|
||||
Event::LinkTls => "link-tls",
|
||||
Event::TxBegin => "tx-begin",
|
||||
Event::TxMail => "tx-mail",
|
||||
Event::TxRcpt => "tx-rcpt",
|
||||
Event::TxEnvelope => "tx-envelope",
|
||||
Event::TxData => "tx-data",
|
||||
Event::TxCommit => "tx-commit",
|
||||
Event::TxRollback => "tx-rollback",
|
||||
Event::ProtocolClient => "protocol-client",
|
||||
Event::ProtocolServer => "protocol-server",
|
||||
Event::FilterResponse => "filter-response",
|
||||
Event::Timeout => "timeout",
|
||||
};
|
||||
String::from(s)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TimeVal {
|
||||
pub sec: i64,
|
||||
pub usec: i64,
|
||||
}
|
||||
|
||||
impl ToString for TimeVal {
|
||||
fn to_string(&self) -> String {
|
||||
format!("{}.{}", self.sec, self.usec)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Entry {
|
||||
V1Report(V1Report),
|
||||
V1Filter(V1Filter),
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
pub fn new(entry: &str) -> Result<(String, Option<Self>), Error> {
|
||||
match parse_entry(entry) {
|
||||
Ok((remainder, entry)) => Ok((remainder.to_string(), Some(entry))),
|
||||
Err(e) => match e {
|
||||
Incomplete(_) => Ok((String::new(), None)),
|
||||
_ => Err(e.into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_event(&self) -> Event {
|
||||
match self {
|
||||
Entry::V1Report(r) => r.event.to_owned(),
|
||||
Entry::V1Filter(f) => f.event.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_session_id(&self) -> SessionId {
|
||||
match self {
|
||||
Entry::V1Report(r) => r.session_id,
|
||||
Entry::V1Filter(f) => f.session_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_disconnect(&self) -> bool {
|
||||
match self {
|
||||
Entry::V1Report(r) => r.event == Event::LinkDisconnect,
|
||||
Entry::V1Filter(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct V1Report {
|
||||
pub timestamp: TimeVal,
|
||||
pub subsystem: Subsystem,
|
||||
pub event: Event,
|
||||
pub session_id: SessionId,
|
||||
pub params: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct V1Filter {
|
||||
pub timestamp: TimeVal,
|
||||
pub subsystem: Subsystem,
|
||||
pub event: Event,
|
||||
pub session_id: SessionId,
|
||||
pub token: Token,
|
||||
pub params: Vec<String>,
|
||||
}
|
||||
|
||||
fn separator(input: &str) -> IResult<&str, &str> {
|
||||
tag("|")(input)
|
||||
}
|
||||
|
||||
fn parse_kind(input: &str) -> IResult<&str, Kind> {
|
||||
alt((
|
||||
value(Kind::Report, tag("report")),
|
||||
value(Kind::Filter, tag("filter")),
|
||||
))(input)
|
||||
}
|
||||
|
||||
fn parse_version(input: &str) -> IResult<&str, Version> {
|
||||
value(Version::V1, tag("1"))(input)
|
||||
}
|
||||
|
||||
fn parse_timestamp(input: &str) -> IResult<&str, TimeVal> {
|
||||
let (input, sec) = map_res(digit1, |s: &str| s.parse::<i64>())(input)?;
|
||||
let (input, _) = tag(".")(input)?;
|
||||
let (input, usec) = map_res(digit1, |s: &str| s.parse::<i64>())(input)?;
|
||||
let timestamp = TimeVal { sec, usec };
|
||||
Ok((input, timestamp))
|
||||
}
|
||||
|
||||
fn parse_subsystem(input: &str) -> IResult<&str, Subsystem> {
|
||||
alt((
|
||||
value(Subsystem::SmtpIn, tag("smtp-in")),
|
||||
value(Subsystem::SmtpOut, tag("smtp-out")),
|
||||
))(input)
|
||||
}
|
||||
|
||||
fn parse_event(input: &str) -> IResult<&str, Event> {
|
||||
alt((
|
||||
value(Event::LinkAuth, tag("link-auth")),
|
||||
value(Event::LinkConnect, tag("link-connect")),
|
||||
value(Event::LinkDisconnect, tag("link-disconnect")),
|
||||
value(Event::LinkIdentify, tag("link-identify")),
|
||||
value(Event::LinkReset, tag("link-reset")),
|
||||
value(Event::LinkTls, tag("link-tls")),
|
||||
value(Event::TxBegin, tag("tx-begin")),
|
||||
value(Event::TxMail, tag("tx-mail")),
|
||||
value(Event::TxRcpt, tag("tx-rcpt")),
|
||||
value(Event::TxEnvelope, tag("tx-envelope")),
|
||||
value(Event::TxData, tag("tx-data")),
|
||||
value(Event::TxCommit, tag("tx-commit")),
|
||||
value(Event::TxRollback, tag("tx-rollback")),
|
||||
value(Event::ProtocolClient, tag("protocol-client")),
|
||||
value(Event::ProtocolServer, tag("protocol-server")),
|
||||
value(Event::FilterResponse, tag("filter-response")),
|
||||
value(Event::Timeout, tag("timeout")),
|
||||
))(input)
|
||||
}
|
||||
|
||||
fn parse_token(input: &str) -> IResult<&str, Token> {
|
||||
map_res(hex_digit1, |s: &str| Token::from_str_radix(s, 16))(input)
|
||||
}
|
||||
|
||||
fn parse_session_id(input: &str) -> IResult<&str, SessionId> {
|
||||
map_res(hex_digit1, |s: &str| SessionId::from_str_radix(s, 16))(input)
|
||||
}
|
||||
|
||||
fn parse_param(input: &str) -> IResult<&str, String> {
|
||||
let (input, _) = separator(input)?;
|
||||
let (input, param) = take_till(is_end_param)(input)?;
|
||||
Ok((input, param.to_string()))
|
||||
}
|
||||
|
||||
fn is_end_param(c: char) -> bool {
|
||||
c == '|' || c == '\r' || c == '\n'
|
||||
}
|
||||
|
||||
fn parse_v1_report(input: &str) -> IResult<&str, Entry> {
|
||||
let (input, timestamp) = parse_timestamp(input)?;
|
||||
let (input, _) = separator(input)?;
|
||||
let (input, subsystem) = parse_subsystem(input)?;
|
||||
let (input, _) = separator(input)?;
|
||||
let (input, event) = parse_event(input)?;
|
||||
let (input, _) = separator(input)?;
|
||||
let (input, session_id) = parse_session_id(input)?;
|
||||
let (input, params) = many0(parse_param)(input)?;
|
||||
let (input, _) = line_ending(input)?;
|
||||
let report = V1Report {
|
||||
timestamp,
|
||||
subsystem,
|
||||
event,
|
||||
session_id,
|
||||
params,
|
||||
};
|
||||
Ok((input, Entry::V1Report(report)))
|
||||
}
|
||||
|
||||
fn parse_v1_filter(input: &str) -> IResult<&str, Entry> {
|
||||
let (input, timestamp) = parse_timestamp(input)?;
|
||||
let (input, _) = separator(input)?;
|
||||
let (input, subsystem) = parse_subsystem(input)?;
|
||||
let (input, _) = separator(input)?;
|
||||
let (input, event) = parse_event(input)?;
|
||||
let (input, _) = separator(input)?;
|
||||
let (input, session_id) = parse_session_id(input)?;
|
||||
let (input, _) = separator(input)?;
|
||||
let (input, token) = parse_token(input)?;
|
||||
let (input, params) = many0(parse_param)(input)?;
|
||||
let (input, _) = line_ending(input)?;
|
||||
let filter = V1Filter {
|
||||
timestamp,
|
||||
subsystem,
|
||||
event,
|
||||
session_id,
|
||||
token,
|
||||
params,
|
||||
};
|
||||
Ok((input, Entry::V1Filter(filter)))
|
||||
}
|
||||
|
||||
fn parse_entry(input: &str) -> IResult<&str, Entry> {
|
||||
let (input, kind) = parse_kind(input)?;
|
||||
let (input, _) = separator(input)?;
|
||||
let (input, version) = parse_version(input)?;
|
||||
let (input, _) = separator(input)?;
|
||||
let (input, entry) = match version {
|
||||
Version::V1 => match kind {
|
||||
Kind::Report => parse_v1_report(input)?,
|
||||
Kind::Filter => parse_v1_filter(input)?,
|
||||
},
|
||||
};
|
||||
Ok((input, entry))
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
// Copyright (c) 2019 Rodolphe Bréard
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
pub struct Error {
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn new(msg: &str) -> Self {
|
||||
Error {
|
||||
message: msg.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(error: std::io::Error) -> Self {
|
||||
Error::new(&format!("IO error: {}", error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::string::String> for Error {
|
||||
fn from(error: std::string::String) -> Self {
|
||||
Error { message: error }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::str::Utf8Error> for Error {
|
||||
fn from(error: std::str::Utf8Error) -> Self {
|
||||
Error::new(&format!("UTF8 error: {}", error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<log::SetLoggerError> for Error {
|
||||
fn from(error: log::SetLoggerError) -> Self {
|
||||
Error::new(&format!("Logger error: {}", error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<nom::Err<(&str, nom::error::ErrorKind)>> for Error {
|
||||
fn from(error: nom::Err<(&str, nom::error::ErrorKind)>) -> Self {
|
||||
let msg = match error {
|
||||
nom::Err::Incomplete(_) => "not enough data".to_string(),
|
||||
nom::Err::Error(c) => format!("{:?}", c),
|
||||
nom::Err::Failure(c) => format!("{:?}", c),
|
||||
};
|
||||
Error::new(&format!("Parsing error: {}", msg))
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
// Copyright (c) 2019 Rodolphe Bréard
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use crate::entry::{Entry, Event, Kind, Subsystem, Version};
|
||||
use crate::errors::Error;
|
||||
use crate::output::FilterOutput;
|
||||
use std::collections::HashSet;
|
||||
|
||||
macro_rules! handle {
|
||||
($self: ident, $obj: ident, $version: expr, $kind: expr, $entry: ident, $output: ident, $session_ctx: ident, $filter_ctx: ident) => {{
|
||||
if $self.version == $version
|
||||
&& $self.kind == $kind
|
||||
&& $self.subsystem == $obj.subsystem
|
||||
&& $self.events.contains(&$obj.event)
|
||||
{
|
||||
($self.action)($output, $entry, $session_ctx, $filter_ctx)?;
|
||||
}
|
||||
Ok(())
|
||||
}};
|
||||
}
|
||||
|
||||
type Callback<S, F> = fn(&mut dyn FilterOutput, &Entry, &mut S, &mut F) -> Result<(), String>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Handler<S, F> {
|
||||
version: Version,
|
||||
pub(crate) kind: Kind,
|
||||
pub(crate) subsystem: Subsystem,
|
||||
pub(crate) events: HashSet<Event>,
|
||||
action: Callback<S, F>,
|
||||
}
|
||||
|
||||
impl<S, F> Handler<S, F> {
|
||||
pub fn new(
|
||||
version: Version,
|
||||
kind: Kind,
|
||||
subsystem: Subsystem,
|
||||
events: &[Event],
|
||||
action: Callback<S, F>,
|
||||
) -> Self {
|
||||
Handler {
|
||||
version,
|
||||
kind,
|
||||
subsystem,
|
||||
events: events.iter().cloned().collect(),
|
||||
action,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(
|
||||
&self,
|
||||
entry: &Entry,
|
||||
output: &mut dyn FilterOutput,
|
||||
session_ctx: &mut S,
|
||||
filter_ctx: &mut F,
|
||||
) -> Result<(), Error> {
|
||||
match entry {
|
||||
Entry::V1Report(report) => handle!(
|
||||
self,
|
||||
report,
|
||||
Version::V1,
|
||||
Kind::Report,
|
||||
entry,
|
||||
output,
|
||||
session_ctx,
|
||||
filter_ctx
|
||||
),
|
||||
Entry::V1Filter(filter) => handle!(
|
||||
self,
|
||||
filter,
|
||||
Version::V1,
|
||||
Kind::Filter,
|
||||
entry,
|
||||
output,
|
||||
session_ctx,
|
||||
filter_ctx
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// Copyright (c) 2019 Rodolphe Bréard
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use crate::entry::Entry;
|
||||
use crate::errors::Error;
|
||||
|
||||
pub trait FilterInput {
|
||||
fn next(&mut self) -> Result<Entry, Error>;
|
||||
}
|
||||
|
||||
mod stdin;
|
||||
pub use stdin::StdIn;
|
|
@ -1,78 +0,0 @@
|
|||
// Copyright (c) 2019 Rodolphe Bréard
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use crate::entry::Entry;
|
||||
use crate::errors::Error;
|
||||
use crate::input::FilterInput;
|
||||
use std::default::Default;
|
||||
use std::io::{self, ErrorKind, Read};
|
||||
use std::str;
|
||||
|
||||
const BUFFER_SIZE: usize = 4096;
|
||||
|
||||
pub struct StdIn {
|
||||
buffer: [u8; BUFFER_SIZE],
|
||||
stdin: io::Stdin,
|
||||
input: String,
|
||||
}
|
||||
|
||||
impl Default for StdIn {
|
||||
fn default() -> Self {
|
||||
StdIn {
|
||||
buffer: [0; BUFFER_SIZE],
|
||||
stdin: io::stdin(),
|
||||
input: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterInput for StdIn {
|
||||
fn next(&mut self) -> Result<Entry, Error> {
|
||||
let mut force_read = false;
|
||||
loop {
|
||||
if force_read || self.input.is_empty() {
|
||||
// Reset the flag
|
||||
force_read = false;
|
||||
// Read stdin in self.buffer
|
||||
self.buffer.copy_from_slice(&[0; BUFFER_SIZE]);
|
||||
let len = match self.stdin.read(&mut self.buffer) {
|
||||
Ok(n) => n,
|
||||
Err(e) => match e.kind() {
|
||||
ErrorKind::Interrupted => {
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
return Err(e.into());
|
||||
}
|
||||
},
|
||||
};
|
||||
if len == 0 {
|
||||
return Err(Error::new("Unable to read on stdin."));
|
||||
}
|
||||
// Put the buffer's content in self.input
|
||||
self.input += match self.buffer.iter().position(|&x| x == 0) {
|
||||
Some(i) => str::from_utf8(&self.buffer[..i]),
|
||||
None => str::from_utf8(&self.buffer),
|
||||
}?;
|
||||
}
|
||||
// Try to build an entry from self.input
|
||||
let (remainder, entry_opt) = Entry::new(&self.input)?;
|
||||
match entry_opt {
|
||||
// We have at least one entry.
|
||||
Some(entry) => {
|
||||
self.input = remainder;
|
||||
return Ok(entry);
|
||||
}
|
||||
// The data is incomplete, no entry could be built.
|
||||
None => {
|
||||
force_read = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,234 +0,0 @@
|
|||
// Copyright (c) 2019 Rodolphe Bréard
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
mod errors;
|
||||
mod handler;
|
||||
mod logger;
|
||||
|
||||
pub mod entry;
|
||||
pub mod input;
|
||||
pub mod output;
|
||||
|
||||
use crate::entry::{Kind, SessionId, Subsystem};
|
||||
use log;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::default::Default;
|
||||
|
||||
pub use crate::errors::Error;
|
||||
pub use crate::handler::Handler;
|
||||
pub use crate::logger::SmtpdLogger;
|
||||
pub use opensmtpd_derive::report;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! register_contexts {
|
||||
($context: ty) => {
|
||||
opensmtpd::register_contexts!($context, $context);
|
||||
};
|
||||
($session_context: ty, $filter_context: ty) => {
|
||||
type OpenSmtpdFilterContextType = $filter_context;
|
||||
type OpenSmtpdSessionContextType = $session_context;
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! register_filter_context_only {
|
||||
($context: ty) => {
|
||||
type OpenSmtpdFilterContextType = $context;
|
||||
type OpenSmtpdSessionContextType = opensmtpd::NoContext;
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! register_session_context_only {
|
||||
($context: ty) => {
|
||||
type OpenSmtpdFilterContextType = opensmtpd::NoContext;
|
||||
type OpenSmtpdSessionContextType = $context;
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! register_no_context {
|
||||
() => {
|
||||
type OpenSmtpdFilterContextType = opensmtpd::NoContext;
|
||||
type OpenSmtpdSessionContextType = opensmtpd::NoContext;
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! simple_filter {
|
||||
($handlers: expr) => {
|
||||
opensmtpd::simple_filter!(
|
||||
log::Level::Info,
|
||||
opensmtpd::NoContext,
|
||||
opensmtpd::NoContext,
|
||||
$handlers
|
||||
);
|
||||
};
|
||||
($filter_ctx: ty, $handlers: expr) => {
|
||||
opensmtpd::simple_filter!(
|
||||
log::Level::Info,
|
||||
opensmtpd::NoContext,
|
||||
$filter_ctx,
|
||||
$handlers
|
||||
);
|
||||
};
|
||||
($sesion_ctx: ty, $filter_ctx: ty, $handlers: expr) => {
|
||||
opensmtpd::simple_filter!(log::Level::Info, $sesion_ctx, $filter_ctx, $handlers);
|
||||
};
|
||||
($log_level: path, $sesion_ctx: ty, $filter_ctx: ty, $handlers: expr) => {
|
||||
let handlers = ($handlers)
|
||||
.iter()
|
||||
.map(|f| f())
|
||||
.collect::<Vec<opensmtpd::Handler<$sesion_ctx, $filter_ctx>>>();
|
||||
let _ = opensmtpd::SmtpdLogger::new().set_level($log_level).init();
|
||||
opensmtpd::Filter::<
|
||||
opensmtpd::input::StdIn,
|
||||
opensmtpd::output::StdOut,
|
||||
$sesion_ctx,
|
||||
$filter_ctx,
|
||||
>::default()
|
||||
.set_handlers(handlers.as_slice())
|
||||
.register_events()
|
||||
.run();
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! fatal_error {
|
||||
($error: ident) => {
|
||||
log::error!("Error: {}", $error);
|
||||
std::process::exit(1);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! insert_events {
|
||||
($handler: ident, $set: ident) => {{
|
||||
for e in $handler.events.iter() {
|
||||
$set.insert(e);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! register_events {
|
||||
($output: expr, $set: ident, $kind: expr, $subsystem: expr) => {
|
||||
for e in $set.iter() {
|
||||
let msg = format!("register|{}|{}|{}", $kind, $subsystem, e.to_string());
|
||||
if let Err(e) = $output.send(&msg) {
|
||||
fatal_error!(e);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct NoContext;
|
||||
|
||||
pub struct Filter<I, O, S, F>
|
||||
where
|
||||
I: crate::input::FilterInput + Default,
|
||||
O: crate::output::FilterOutput + Default,
|
||||
S: Default,
|
||||
F: Default,
|
||||
{
|
||||
input: I,
|
||||
output: O,
|
||||
session_ctx: HashMap<SessionId, S>,
|
||||
filter_ctx: F,
|
||||
handlers: Vec<Handler<S, F>>,
|
||||
}
|
||||
|
||||
impl<I, O, S, F> Default for Filter<I, O, S, F>
|
||||
where
|
||||
I: crate::input::FilterInput + Default,
|
||||
O: crate::output::FilterOutput + Default,
|
||||
S: Default,
|
||||
F: Default,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Filter {
|
||||
input: I::default(),
|
||||
output: O::default(),
|
||||
session_ctx: HashMap::new(),
|
||||
filter_ctx: F::default(),
|
||||
handlers: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, O, S, F> Filter<I, O, S, F>
|
||||
where
|
||||
I: crate::input::FilterInput + Default,
|
||||
O: crate::output::FilterOutput + Default,
|
||||
S: Clone + Default,
|
||||
F: Clone + Default,
|
||||
{
|
||||
pub fn set_handlers(&mut self, handlers: &[Handler<S, F>]) -> &mut Self {
|
||||
self.handlers = handlers.to_vec();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn register_events(&mut self) -> &mut Self {
|
||||
let mut report_smtp_in = HashSet::new();
|
||||
let mut report_smtp_out = HashSet::new();
|
||||
let mut filter_smtp_in = HashSet::new();
|
||||
let mut filter_smtp_out = HashSet::new();
|
||||
for h in self.handlers.iter() {
|
||||
match h.kind {
|
||||
Kind::Report => match h.subsystem {
|
||||
Subsystem::SmtpIn => insert_events!(h, report_smtp_in),
|
||||
Subsystem::SmtpOut => insert_events!(h, report_smtp_out),
|
||||
},
|
||||
Kind::Filter => match h.subsystem {
|
||||
Subsystem::SmtpIn => insert_events!(h, filter_smtp_in),
|
||||
Subsystem::SmtpOut => insert_events!(h, filter_smtp_out),
|
||||
},
|
||||
};
|
||||
}
|
||||
register_events!(self.output, report_smtp_in, "report", "smtp-in");
|
||||
register_events!(self.output, report_smtp_out, "report", "smtp-out");
|
||||
register_events!(self.output, filter_smtp_in, "filter", "smtp-in");
|
||||
register_events!(self.output, filter_smtp_out, "filter", "smtp-out");
|
||||
self
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
loop {
|
||||
match self.input.next() {
|
||||
Ok(entry) => {
|
||||
log::debug!("{:?}", entry);
|
||||
let session_id = entry.get_session_id();
|
||||
let mut session_ctx = match self.session_ctx.get_mut(&session_id) {
|
||||
Some(c) => c,
|
||||
None => {
|
||||
self.session_ctx.insert(session_id, S::default());
|
||||
self.session_ctx.get_mut(&session_id).unwrap()
|
||||
}
|
||||
};
|
||||
for h in self.handlers.iter() {
|
||||
match h.send(
|
||||
&entry,
|
||||
&mut self.output,
|
||||
&mut session_ctx,
|
||||
&mut self.filter_ctx,
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
log::warn!("Warning: {}", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
if entry.is_disconnect() {
|
||||
self.session_ctx.remove(&session_id);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
fatal_error!(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
// Copyright (c) 2019 Rodolphe Bréard
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use crate::errors::Error;
|
||||
use log::{Level, Metadata, Record};
|
||||
use std::io::{self, Write};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SmtpdLogger {
|
||||
level: Level,
|
||||
}
|
||||
|
||||
impl SmtpdLogger {
|
||||
pub fn new() -> Self {
|
||||
SmtpdLogger::default()
|
||||
}
|
||||
|
||||
pub fn set_level(&mut self, level: Level) -> Self {
|
||||
self.level = level;
|
||||
self.clone()
|
||||
}
|
||||
|
||||
pub fn init(self) -> Result<(), Error> {
|
||||
let level = self.level.to_level_filter();
|
||||
log::set_boxed_logger(Box::new(self))?;
|
||||
log::set_max_level(level);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SmtpdLogger {
|
||||
fn default() -> Self {
|
||||
SmtpdLogger { level: Level::Warn }
|
||||
}
|
||||
}
|
||||
|
||||
impl log::Log for SmtpdLogger {
|
||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||
metadata.level() <= self.level
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
let stderr = io::stderr();
|
||||
let mut handle = stderr.lock();
|
||||
writeln!(handle, "{}: {}", record.level(), record.args()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright (c) 2019 Rodolphe Bréard
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use crate::errors::Error;
|
||||
|
||||
pub trait FilterOutput {
|
||||
fn send(&mut self, msg: &str) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
mod null;
|
||||
mod stdout;
|
||||
|
||||
pub use null::NullOutput;
|
||||
pub use stdout::{StdErr, StdOut};
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright (c) 2019 Rodolphe Bréard
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use crate::errors::Error;
|
||||
use crate::output::FilterOutput;
|
||||
use std::default::Default;
|
||||
|
||||
pub struct NullOutput {}
|
||||
|
||||
impl Default for NullOutput {
|
||||
fn default() -> Self {
|
||||
NullOutput {}
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterOutput for NullOutput {
|
||||
fn send(&mut self, _msg: &str) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
// Copyright (c) 2019 Rodolphe Bréard
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
use crate::errors::Error;
|
||||
use crate::output::FilterOutput;
|
||||
use std::default::Default;
|
||||
|
||||
macro_rules! new_stdout {
|
||||
($name: ident, $out: ident) => {
|
||||
pub struct $name {}
|
||||
|
||||
impl Default for $name {
|
||||
fn default() -> Self {
|
||||
$name {}
|
||||
}
|
||||
}
|
||||
|
||||
impl FilterOutput for $name {
|
||||
fn send(&mut self, msg: &str) -> Result<(), Error> {
|
||||
$out!("{}", msg);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
new_stdout!(StdOut, println);
|
||||
new_stdout!(StdErr, eprintln);
|
Reference in a new issue