Refactor the library

Threads are a bad idea because for now the filter API is not guaranteed
to be state-less. The interface is now synchronous, which should be
enough for most filters.
The refactoring brought other changes, the most important being the
concept of modular input sources and output destination and the complete
rewrite of the procedural macro.
This commit is contained in:
Rodolphe Breard 2019-09-17 16:45:04 +02:00
parent 988f028c23
commit 45639f18c0
18 changed files with 486 additions and 400 deletions

View file

@ -7,12 +7,10 @@ Rust binding for [OpenSMTPD] filters.
This is a work in progress, the API is **not** stabilized yet.
- [ ] Thread-pool
- [x] Reports
- [ ] Filters
- [x] Session-level context
- [ ] Pool-level context
- [ ] Filter-level context
- [ ] Session-level context
[OpenSMTPD]: https://www.opensmtpd.org/

View file

@ -1,6 +1,6 @@
[package]
name = "opensmtpd_derive"
version = "0.1.0"
version = "0.2.0"
authors = ["Rodolphe Bréard <rodolphe@what.tf>"]
edition = "2018"
description = "Interface for OpenSMTPD filters"
@ -15,5 +15,5 @@ include = ["src/**/*", "Cargo.toml", "LICENSE-*.txt"]
proc-macro = true
[dependencies]
quote = "0.6"
syn = { version = "0.15", features = ["full"] }
quote = "1.0"
syn = { version = "1.0", features = ["full", "extra-traits"] }

View file

@ -10,70 +10,152 @@ extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{parenthesized, parse_macro_input, ExprArray, Ident, ItemFn, Result, Token, TypePath};
fn get_type(
params: &syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>,
) -> Result<(Box<syn::Type>, syn::Type), ()> {
match params.iter().count() {
1 => {
let ctx = Box::new(syn::Type::Verbatim(syn::TypeVerbatim {
tts: quote! {
opensmtpd::NoContext
},
}));
let cb = syn::Type::Verbatim(syn::TypeVerbatim {
tts: quote! { opensmtpd::Callback::NoCtx },
});
Ok((ctx, cb))
}
2 => match params.iter().next().unwrap() {
syn::FnArg::Captured(ref a) => match &a.ty {
syn::Type::Reference(r) => {
let cb = match r.mutability {
Some(_) => syn::Type::Verbatim(syn::TypeVerbatim {
tts: quote! { opensmtpd::Callback::CtxMut },
}),
None => syn::Type::Verbatim(syn::TypeVerbatim {
tts: quote! { opensmtpd::Callback::Ctx },
}),
};
Ok((r.elem.clone(), cb))
}
_ => Err(()),
},
_ => Err(()),
},
_ => Err(()),
#[derive(Debug)]
struct OpenSmtpdAttributes {
version: Ident,
subsystem: Ident,
events: Punctuated<Ident, Token![,]>,
}
impl Parse for OpenSmtpdAttributes {
fn parse(input: ParseStream) -> Result<Self> {
let version = input.parse()?;
let _: Token![,] = input.parse()?;
let subsystem = input.parse()?;
let _: Token![,] = input.parse()?;
let _match: Token![match] = input.parse()?;
let content;
let _ = parenthesized!(content in input);
let events = content.parse_terminated(Ident::parse)?;
Ok(OpenSmtpdAttributes {
version,
subsystem,
events,
})
}
}
#[proc_macro_attribute]
pub fn event(attr: TokenStream, input: TokenStream) -> TokenStream {
let attr = attr.to_string();
let item = parse_macro_input!(input as ItemFn);
let fn_name = &item.ident;
let fn_params = &item.decl.inputs;
let (ctx_type, callback_type) = match get_type(fn_params) {
Ok(t) => t,
Err(_e) => {
panic!();
impl OpenSmtpdAttributes {
fn get_version(&self) -> String {
format!(
"opensmtpd::entry::Version::{}",
self.version.to_string().to_uppercase()
)
}
fn get_subsystem(&self) -> String {
let subsystem = match self.subsystem.to_string().as_str() {
"smtp_in" => "SmtpIn",
"smtp_out" => "SmtpOut",
_ => "",
};
format!("opensmtpd::entry::Subsystem::{}", subsystem)
}
fn get_events(&self) -> String {
let events = if self
.events
.iter()
.find(|&e| e.to_string().to_lowercase().as_str() == "all")
.is_some()
{
let lst = [
"LinkAuth",
"LinkConnect",
"LinkDisconnect",
"LinkIdentify",
"LinkReset",
"LinkTls",
"TxBegin",
"TxMail",
"TxRcpt",
"TxEnvelope",
"TxData",
"TxCommit",
"TxRollback",
"ProtocolClient",
"ProtocolServer",
"FilterResponse",
"Timeout",
];
lst.iter()
.map(|e| format!("opensmtpd::entry::Event::{}", e))
.collect::<Vec<String>>()
} else {
self.events
.iter()
.map(|e| {
let name = match e.to_string().as_str() {
"link_auth" => "LinkAuth",
"link_connect" => "LinkConnect",
"link_disconnect" => "LinkDisconnect",
"link_identify" => "LinkIdentify",
"link_reset" => "LinkReset",
"link_tls" => "LinkTls",
"tx_begin" => "TxBegin",
"tx_mail" => "TxMail",
"tx_rcpt" => "TxRcpt",
"tx_envelope" => "TxEnvelope",
"tx_data" => "TxData",
"tx_commit" => "TxCommit",
"tx_rollback" => "TxRollback",
"protocol_client" => "ProtocolClient",
"protocol_server" => "ProtocolServer",
"filter_response" => "FilterResponse",
"timeout" => "Timeout",
_ => "",
};
format!("opensmtpd::entry::Event::{}", name)
})
.collect::<Vec<String>>()
};
format!("[{}]", events.join(", "))
}
}
macro_rules! parse_item {
($item: expr, $type: ty) => {
match syn::parse_str::<$type>($item) {
Ok(i) => i,
Err(e) => {
return TokenStream::from(e.to_compile_error());
}
}
};
}
#[proc_macro_attribute]
pub fn report(attr: TokenStream, input: TokenStream) -> TokenStream {
let attr = parse_macro_input!(attr as OpenSmtpdAttributes);
let version = parse_item!(&attr.get_version(), TypePath);
let subsystem = parse_item!(&attr.get_subsystem(), TypePath);
let events = parse_item!(&attr.get_events(), ExprArray);
let item = parse_macro_input!(input as ItemFn);
let fn_name = &item.sig.ident;
let fn_params = &item.sig.inputs;
let fn_body = &item.block;
let fn_output = &item.decl.output;
let output = quote! {
fn #fn_name() -> opensmtpd::EventHandler<#ctx_type> {
opensmtpd::EventHandler::new(
#attr,
#callback_type(|#fn_params| #fn_output #fn_body)
fn #fn_name() -> opensmtpd::Handler {
opensmtpd::Handler::new(
#version,
opensmtpd::entry::Kind::Report,
#subsystem,
&#events,
|_output: &mut dyn opensmtpd::output::FilterOutput, entry: &opensmtpd::entry::Entry,| {
// TODO: look at `item.sig.output` and adapt the calling scheme.
// example: if no return, add `Ok(())`.
// https://docs.rs/syn/1.0.5/syn/struct.Signature.html
let inner_fn = |#fn_params| -> Result<(), String> {
#fn_body
};
inner_fn(entry)
},
)
}
};
output.into()
}
#[proc_macro_attribute]
pub fn report(attr: TokenStream, input: TokenStream) -> TokenStream {
event(attr, input)
}

View file

@ -1,6 +1,6 @@
[package]
name = "opensmtpd"
version = "0.1.0"
version = "0.2.0"
authors = ["Rodolphe Bréard <rodolphe@what.tf>"]
edition = "2018"
description = "Interface for OpenSMTPD filters"
@ -14,12 +14,12 @@ include = ["src/**/*", "Cargo.toml", "LICENSE-*.txt"]
[dependencies]
log = {version = "0.4", features = ["std"]}
nom = "5.0"
opensmtpd_derive = { path = "../opensmtpd-derive", version = "0.1" }
opensmtpd_derive = { path = "../opensmtpd-derive", version = "0.2" }
[[example]]
name = "dummy"
path = "examples/dummy.rs"
name = "echo"
path = "examples/echo.rs"
[[example]]
name = "counter"
path = "examples/session_event_counter.rs"
#[[example]]
#name = "counter"
#path = "examples/session_event_counter.rs"

View file

@ -1,19 +0,0 @@
use log::{debug, info, Level};
use opensmtpd::{event, handlers, Entry, SmtpIn, SmtpdLogger};
#[event(Any)]
fn on_event(entry: &Entry) {
debug!("Event received: {:?}", entry);
}
#[event(LinkConnect)]
fn on_connect(entry: &Entry) {
info!("New client on session {:x}.", entry.get_session_id());
}
fn main() {
let _ = SmtpdLogger::new().set_level(Level::Debug).init();
SmtpIn::new()
.event_handlers(handlers!(on_event, on_connect))
.run();
}

View file

@ -0,0 +1,18 @@
use opensmtpd::entry::Entry;
use opensmtpd::{report, simple_filter};
#[report(v1, smtp_in, match(all))]
fn echo_handler(entry: &Entry) -> Result<(), String> {
log::info!("TEST ENTRY: {:?}", entry);
Ok(())
}
#[report(v1, smtp_in, match(link_disconnect))]
fn test(entry: &Entry) {
log::info!("HAZ LINK DISCONNECT: {:?}", entry);
Ok(()) // TODO: REMOVE ME!
}
fn main() {
simple_filter!(echo_handler, test);
}

View file

@ -1,5 +1,4 @@
use log::{info, Level};
use opensmtpd::{handlers, report, Entry, SmtpIn, SmtpdLogger};
use opensmtpd::{report, simple_filter};
#[derive(Clone, Default)]
struct MyContext {
@ -13,6 +12,5 @@ fn on_report(ctx: &mut MyContext, entry: &Entry) {
}
fn main() {
let _ = SmtpdLogger::new().set_level(Level::Debug).init();
SmtpIn::new().event_handlers(handlers!(on_report)).run();
simple_filter!(vec![on_report]);
}

View file

@ -8,31 +8,32 @@
use crate::errors::Error;
use nom::branch::alt;
use nom::bytes::complete::{tag, take_till};
use nom::character::complete::{digit1, hex_digit1, line_ending};
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;
#[derive(Clone, Debug, PartialEq)]
enum Version {
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Version {
V1,
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Kind {
Report,
Filter,
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Subsystem {
SmtpIn,
SmtpOut,
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum Event {
LinkAuth,
LinkConnect,
@ -114,16 +115,17 @@ pub enum Entry {
V1Filter(V1Filter),
}
impl FromStr for Entry {
type Err = Error;
fn from_str(entry: &str) -> Result<Self, Self::Err> {
let (_, res) = parse_entry(entry)?;
Ok(res)
}
}
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(),
@ -244,7 +246,7 @@ fn parse_v1_report(input: &str) -> IResult<&str, Entry> {
let (input, _) = separator(input)?;
let (input, session_id) = parse_session_id(input)?;
let (input, params) = many0(parse_param)(input)?;
let _ = line_ending(input)?;
let (input, _) = line_ending(input)?;
let report = V1Report {
timestamp,
subsystem,
@ -266,7 +268,7 @@ fn parse_v1_filter(input: &str) -> IResult<&str, Entry> {
let (input, _) = separator(input)?;
let (input, token) = parse_token(input)?;
let (input, params) = many0(parse_param)(input)?;
let _ = line_ending(input)?;
let (input, _) = line_ending(input)?;
let filter = V1Filter {
timestamp,
subsystem,

View file

@ -6,8 +6,6 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use crate::entry::Entry;
use crate::event_handlers::EventHandler;
use std::fmt;
pub struct Error {
@ -34,6 +32,18 @@ impl From<std::io::Error> for 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))
@ -50,15 +60,3 @@ impl From<nom::Err<(&str, nom::error::ErrorKind)>> for Error {
Error::new(&format!("Parsing error: {}", msg))
}
}
impl From<std::sync::mpsc::SendError<Entry>> for Error {
fn from(error: std::sync::mpsc::SendError<Entry>) -> Self {
Error::new(&format!("IO error: {}", error))
}
}
impl<T> From<std::sync::mpsc::SendError<EventHandler<T>>> for Error {
fn from(error: std::sync::mpsc::SendError<EventHandler<T>>) -> Self {
Error::new(&format!("IO error: {}", error))
}
}

View file

@ -1,96 +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};
use std::str::FromStr;
#[derive(Clone, Debug, PartialEq)]
pub enum MatchEvent {
Evt(Vec<Event>),
All,
}
#[derive(Clone)]
pub enum Callback<T> {
NoCtx(fn(&Entry)),
Ctx(fn(&T, &Entry)),
CtxMut(fn(&mut T, &Entry)),
}
#[derive(Clone)]
pub struct EventHandler<T> {
pub(crate) event: MatchEvent,
callback: Callback<T>,
}
fn get_events_from_string(event_str: &str) -> MatchEvent {
let mut events = Vec::new();
for name in event_str.split(" , ") {
match name {
"Any" | "All" => {
return MatchEvent::All;
}
_ => {
if let Ok(e) = Event::from_str(name) {
events.push(e);
}
}
}
}
MatchEvent::Evt(events)
}
impl<T: Clone + Default> EventHandler<T> {
pub fn new(event_str: &str, callback: Callback<T>) -> Self {
EventHandler {
event: get_events_from_string(event_str),
callback,
}
}
pub fn is_callable(&self, event: &Event) -> bool {
match &self.event {
MatchEvent::All => true,
MatchEvent::Evt(v) => v.contains(&event),
}
}
pub fn call(&self, entry: &Entry, context: &mut T) {
match self.callback {
Callback::NoCtx(f) => f(entry),
Callback::Ctx(f) => f(context, entry),
Callback::CtxMut(f) => f(context, entry),
};
}
}
#[cfg(test)]
mod test {
use crate::*;
#[test]
fn test_eventhandler_build_noctx() {
EventHandler::new("Any", Callback::NoCtx::<NoContext>(|_entry: &Entry| {}));
}
#[test]
fn test_eventhandler_build_ctx() {
EventHandler::new(
"Any",
Callback::Ctx(|_context: &NoContext, _entry: &Entry| {}),
);
}
#[test]
fn test_eventhandler_build_ctxmut() {
EventHandler::new(
"Any",
Callback::CtxMut(|_context: &mut NoContext, _entry: &Entry| {}),
);
}
}

65
opensmtpd/src/handler.rs Normal file
View file

@ -0,0 +1,65 @@
// 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) => {{
if $self.version == $version
&& $self.kind == $kind
&& $self.subsystem == $obj.subsystem
&& $self.events.contains(&$obj.event)
{
($self.action)($output, $entry)?;
}
Ok(())
}};
}
type Callback = fn(&mut dyn FilterOutput, &Entry) -> Result<(), String>;
#[derive(Clone)]
pub struct Handler {
version: Version,
kind: Kind,
subsystem: Subsystem,
events: HashSet<Event>,
action: Callback,
}
impl Handler {
pub fn new(
version: Version,
kind: Kind,
subsystem: Subsystem,
events: &[Event],
action: Callback,
) -> Self {
Handler {
version,
kind,
subsystem,
events: events.iter().cloned().collect(),
action,
}
}
pub fn send(&self, entry: &Entry, output: &mut dyn FilterOutput) -> Result<(), Error> {
match entry {
Entry::V1Report(report) => {
handle!(self, report, Version::V1, Kind::Report, entry, output)
}
Entry::V1Filter(filter) => {
handle!(self, filter, Version::V1, Kind::Filter, entry, output)
}
}
}
}

View file

@ -6,6 +6,12 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
pub enum Response {
None,
use crate::entry::Entry;
use crate::errors::Error;
pub trait FilterInput {
fn next(&mut self) -> Result<Entry, Error>;
}
mod stdin;
pub use stdin::StdIn;

View file

@ -0,0 +1,66 @@
// 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, 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() {
// Read stdin in self.buffer
self.buffer.copy_from_slice(&[0; BUFFER_SIZE]);
let len = self.stdin.read(&mut self.buffer)?;
if len == 0 {
continue;
}
// 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;
}
};
}
}
}

View file

@ -6,160 +6,101 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
mod entry;
mod errors;
mod event_handlers;
mod handler;
mod logger;
mod response;
mod session_handler;
use crate::session_handler::SessionHandler;
use log::{error, warn};
use std::collections::HashMap;
use std::io;
use std::str::FromStr;
use std::sync::mpsc;
use std::thread;
pub mod entry;
pub mod input;
pub mod output;
use log;
use std::default::Default;
pub use crate::entry::{Entry, Event};
pub use crate::errors::Error;
pub use crate::event_handlers::{Callback, EventHandler, MatchEvent};
pub use crate::handler::Handler;
pub use crate::logger::SmtpdLogger;
pub use crate::response::Response;
pub use opensmtpd_derive::{event, report};
pub use opensmtpd_derive::report;
#[macro_export]
macro_rules! handlers {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
macro_rules! simple_filter {
($( $x:expr ),*) => {
opensmtpd::simple_filter_log_level!(log::Level::Info $(,$x)*);
};
}
#[macro_export]
macro_rules! simple_filter_log_level {
($log_level: expr, $( $x:expr ),*) => {
let mut handlers = Vec::new();
$(
temp_vec.push(($x)());
)*
temp_vec
}
handlers.push(($x)());
)*;
let _ = opensmtpd::SmtpdLogger::new()
.set_level($log_level)
.init();
opensmtpd::Filter::<opensmtpd::input::StdIn, opensmtpd::output::StdOut>::default().set_handlers(&handlers).register_events().run();
};
}
#[derive(Clone, Default)]
pub struct NoContext;
#[derive(Default)]
pub struct SmtpIn<T> {
sessions: HashMap<u64, (mpsc::Sender<Entry>, thread::JoinHandle<()>)>,
event_handlers: Vec<EventHandler<T>>,
pub struct Filter<I, O>
where
I: crate::input::FilterInput + Default,
O: crate::output::FilterOutput + Default,
{
input: I,
output: O,
handlers: Vec<Handler>,
}
impl<T: Clone + Default + 'static> SmtpIn<T> {
fn register_events(&self) {
let mut evts = Vec::new();
for eh in self.event_handlers.iter() {
match eh.event {
MatchEvent::Evt(ref v) => {
for e in v.iter() {
evts.push(e);
impl<I, O> Default for Filter<I, O>
where
I: crate::input::FilterInput + Default,
O: crate::output::FilterOutput + Default,
{
fn default() -> Self {
Filter {
input: I::default(),
output: O::default(),
handlers: Vec::new(),
}
}
MatchEvent::All => {
println!("register|report|smtp-in|*");
evts.clear();
break;
}
}
}
evts.dedup();
for e in evts.iter() {
println!("register|report|smtp-in|{}", e.to_string());
}
// TODO: register filters
// println!("register|filter|smtp-in|{}", "name");
println!("register|ready");
}
}
/// Read a line from the standard input.
/// Since EOF should not append, it is considered as an error.
fn read(&self) -> Result<String, Error> {
let mut input = String::new();
let nb = io::stdin().read_line(&mut input)?;
match nb {
0 => Err(Error::new("end of file")),
_ => Ok(input),
}
}
/// Dispatch the entry into its session's thread. If such thread does not
/// already exists, creates it.
fn dispatch(&mut self, input: &str) -> Result<(), Error> {
let entry = Entry::from_str(input)?;
let id = entry.get_session_id();
let disconnect = entry.is_disconnect();
let channel = match self.sessions.get(&id) {
Some((r, _)) => r,
None => {
let (handlers_tx, handlers_rx) = mpsc::channel();
let (entry_tx, entry_rx) = mpsc::channel();
let name = entry.get_session_id().to_string();
let handle = thread::Builder::new().name(name).spawn(move || {
SessionHandler::new(entry_rx, &handlers_rx).read_entries();
})?;
for h in self.event_handlers.iter() {
handlers_tx.send(h.clone())?;
}
self.sessions
.insert(entry.get_session_id(), (entry_tx, handle));
let (r, _) = &self.sessions[&entry.get_session_id()];
r
}
};
channel.send(entry)?;
if disconnect {
let _ = self.sessions.remove(&id);
}
Ok(())
}
/// Allow each child thread to exit gracefully. First, the session table is
/// drained so all the references to the senders are dropped, which will
/// cause the receivers threads to exit. Then, we uses the join handlers in
/// order to wait for the actual exit.
fn graceful_exit_children(&mut self) {
let mut handles = Vec::new();
for (_, (_, h)) in self.sessions.drain() {
handles.push(h);
}
for h in handles {
let _ = h.join();
}
}
pub fn new() -> Self {
SmtpIn {
sessions: HashMap::new(),
event_handlers: Vec::new(),
}
}
pub fn event_handlers(&mut self, handlers: Vec<EventHandler<T>>) -> &mut Self {
self.event_handlers = handlers.to_owned();
impl<I, O> Filter<I, O>
where
I: crate::input::FilterInput + Default,
O: crate::output::FilterOutput + Default,
{
pub fn set_handlers(&mut self, handlers: &[Handler]) -> &mut Self {
self.handlers = handlers.to_vec();
self
}
pub fn register_events(&mut self) -> &mut Self {
// TODO: use self.output to register events
self
}
/// Run the infinite loop that will read and process input from stdin.
pub fn run(&mut self) {
self.register_events();
loop {
let line = match self.read() {
Ok(l) => l,
match self.input.next() {
Ok(entry) => {
log::debug!("{:?}", entry);
for h in self.handlers.iter() {
match h.send(&entry, &mut self.output) {
Ok(_) => {}
Err(e) => {
self.graceful_exit_children();
error!("{}", e);
log::warn!("Warning: {}", e);
}
};
}
}
Err(e) => {
log::error!("Error: {}", e);
std::process::exit(1);
}
};
match self.dispatch(&line) {
Ok(_) => {}
Err(e) => warn!("{}", e),
}
}
}
}

19
opensmtpd/src/output.rs Normal file
View file

@ -0,0 +1,19 @@
// 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};

View file

@ -0,0 +1,25 @@
// 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(())
}
}

View file

@ -0,0 +1,33 @@
// 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);

View file

@ -1,50 +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::event_handlers::EventHandler;
use log::debug;
use std::sync::mpsc;
use std::thread;
pub struct SessionHandler<T> {
entry_rx: mpsc::Receiver<Entry>,
event_handlers: Vec<EventHandler<T>>,
}
impl<T: Clone + Default> SessionHandler<T> {
pub fn new(
entry_rx: mpsc::Receiver<Entry>,
handlers_rx: &mpsc::Receiver<EventHandler<T>>,
) -> Self {
debug!(
"New thread for session {}",
thread::current().name().unwrap()
);
let mut event_handlers = Vec::new();
for h in handlers_rx.iter() {
debug!("Event handler registered");
event_handlers.push(h);
}
SessionHandler {
entry_rx,
event_handlers,
}
}
pub fn read_entries(&self) {
let mut context: T = Default::default();
for e in self.entry_rx.iter() {
for h in self.event_handlers.iter() {
if h.is_callable(&e.get_event()) {
h.call(&e, &mut context);
}
}
}
}
}