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:
parent
988f028c23
commit
45639f18c0
18 changed files with 486 additions and 400 deletions
|
@ -7,12 +7,10 @@ Rust binding for [OpenSMTPD] filters.
|
||||||
|
|
||||||
This is a work in progress, the API is **not** stabilized yet.
|
This is a work in progress, the API is **not** stabilized yet.
|
||||||
|
|
||||||
- [ ] Thread-pool
|
|
||||||
- [x] Reports
|
- [x] Reports
|
||||||
- [ ] Filters
|
- [ ] Filters
|
||||||
- [x] Session-level context
|
|
||||||
- [ ] Pool-level context
|
|
||||||
- [ ] Filter-level context
|
- [ ] Filter-level context
|
||||||
|
- [ ] Session-level context
|
||||||
|
|
||||||
|
|
||||||
[OpenSMTPD]: https://www.opensmtpd.org/
|
[OpenSMTPD]: https://www.opensmtpd.org/
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "opensmtpd_derive"
|
name = "opensmtpd_derive"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
authors = ["Rodolphe Bréard <rodolphe@what.tf>"]
|
authors = ["Rodolphe Bréard <rodolphe@what.tf>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Interface for OpenSMTPD filters"
|
description = "Interface for OpenSMTPD filters"
|
||||||
|
@ -15,5 +15,5 @@ include = ["src/**/*", "Cargo.toml", "LICENSE-*.txt"]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
quote = "0.6"
|
quote = "1.0"
|
||||||
syn = { version = "0.15", features = ["full"] }
|
syn = { version = "1.0", features = ["full", "extra-traits"] }
|
||||||
|
|
|
@ -10,70 +10,152 @@ extern crate proc_macro;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
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(
|
#[derive(Debug)]
|
||||||
params: &syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>,
|
struct OpenSmtpdAttributes {
|
||||||
) -> Result<(Box<syn::Type>, syn::Type), ()> {
|
version: Ident,
|
||||||
match params.iter().count() {
|
subsystem: Ident,
|
||||||
1 => {
|
events: Punctuated<Ident, Token![,]>,
|
||||||
let ctx = Box::new(syn::Type::Verbatim(syn::TypeVerbatim {
|
}
|
||||||
tts: quote! {
|
|
||||||
opensmtpd::NoContext
|
impl Parse for OpenSmtpdAttributes {
|
||||||
},
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
}));
|
let version = input.parse()?;
|
||||||
let cb = syn::Type::Verbatim(syn::TypeVerbatim {
|
let _: Token![,] = input.parse()?;
|
||||||
tts: quote! { opensmtpd::Callback::NoCtx },
|
let subsystem = input.parse()?;
|
||||||
});
|
let _: Token![,] = input.parse()?;
|
||||||
Ok((ctx, cb))
|
let _match: Token![match] = input.parse()?;
|
||||||
}
|
let content;
|
||||||
2 => match params.iter().next().unwrap() {
|
let _ = parenthesized!(content in input);
|
||||||
syn::FnArg::Captured(ref a) => match &a.ty {
|
let events = content.parse_terminated(Ident::parse)?;
|
||||||
syn::Type::Reference(r) => {
|
Ok(OpenSmtpdAttributes {
|
||||||
let cb = match r.mutability {
|
version,
|
||||||
Some(_) => syn::Type::Verbatim(syn::TypeVerbatim {
|
subsystem,
|
||||||
tts: quote! { opensmtpd::Callback::CtxMut },
|
events,
|
||||||
}),
|
})
|
||||||
None => syn::Type::Verbatim(syn::TypeVerbatim {
|
|
||||||
tts: quote! { opensmtpd::Callback::Ctx },
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
Ok((r.elem.clone(), cb))
|
|
||||||
}
|
|
||||||
_ => Err(()),
|
|
||||||
},
|
|
||||||
_ => Err(()),
|
|
||||||
},
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
impl OpenSmtpdAttributes {
|
||||||
pub fn event(attr: TokenStream, input: TokenStream) -> TokenStream {
|
fn get_version(&self) -> String {
|
||||||
let attr = attr.to_string();
|
format!(
|
||||||
let item = parse_macro_input!(input as ItemFn);
|
"opensmtpd::entry::Version::{}",
|
||||||
let fn_name = &item.ident;
|
self.version.to_string().to_uppercase()
|
||||||
let fn_params = &item.decl.inputs;
|
)
|
||||||
let (ctx_type, callback_type) = match get_type(fn_params) {
|
}
|
||||||
Ok(t) => t,
|
|
||||||
Err(_e) => {
|
fn get_subsystem(&self) -> String {
|
||||||
panic!();
|
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_body = &item.block;
|
||||||
let fn_output = &item.decl.output;
|
|
||||||
let output = quote! {
|
let output = quote! {
|
||||||
fn #fn_name() -> opensmtpd::EventHandler<#ctx_type> {
|
fn #fn_name() -> opensmtpd::Handler {
|
||||||
opensmtpd::EventHandler::new(
|
opensmtpd::Handler::new(
|
||||||
#attr,
|
#version,
|
||||||
#callback_type(|#fn_params| #fn_output #fn_body)
|
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()
|
output.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
|
||||||
pub fn report(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|
||||||
event(attr, input)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "opensmtpd"
|
name = "opensmtpd"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
authors = ["Rodolphe Bréard <rodolphe@what.tf>"]
|
authors = ["Rodolphe Bréard <rodolphe@what.tf>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Interface for OpenSMTPD filters"
|
description = "Interface for OpenSMTPD filters"
|
||||||
|
@ -14,12 +14,12 @@ include = ["src/**/*", "Cargo.toml", "LICENSE-*.txt"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = {version = "0.4", features = ["std"]}
|
log = {version = "0.4", features = ["std"]}
|
||||||
nom = "5.0"
|
nom = "5.0"
|
||||||
opensmtpd_derive = { path = "../opensmtpd-derive", version = "0.1" }
|
opensmtpd_derive = { path = "../opensmtpd-derive", version = "0.2" }
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "dummy"
|
name = "echo"
|
||||||
path = "examples/dummy.rs"
|
path = "examples/echo.rs"
|
||||||
|
|
||||||
[[example]]
|
#[[example]]
|
||||||
name = "counter"
|
#name = "counter"
|
||||||
path = "examples/session_event_counter.rs"
|
#path = "examples/session_event_counter.rs"
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
18
opensmtpd/examples/echo.rs
Normal file
18
opensmtpd/examples/echo.rs
Normal 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);
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
use log::{info, Level};
|
use opensmtpd::{report, simple_filter};
|
||||||
use opensmtpd::{handlers, report, Entry, SmtpIn, SmtpdLogger};
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
struct MyContext {
|
struct MyContext {
|
||||||
|
@ -13,6 +12,5 @@ fn on_report(ctx: &mut MyContext, entry: &Entry) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let _ = SmtpdLogger::new().set_level(Level::Debug).init();
|
simple_filter!(vec![on_report]);
|
||||||
SmtpIn::new().event_handlers(handlers!(on_report)).run();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,31 +8,32 @@
|
||||||
|
|
||||||
use crate::errors::Error;
|
use crate::errors::Error;
|
||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::{tag, take_till};
|
use nom::bytes::streaming::{tag, take_till};
|
||||||
use nom::character::complete::{digit1, hex_digit1, line_ending};
|
use nom::character::streaming::{digit1, hex_digit1, line_ending};
|
||||||
use nom::combinator::{map_res, value};
|
use nom::combinator::{map_res, value};
|
||||||
use nom::multi::many0;
|
use nom::multi::many0;
|
||||||
|
use nom::Err::Incomplete;
|
||||||
use nom::IResult;
|
use nom::IResult;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
enum Version {
|
pub enum Version {
|
||||||
V1,
|
V1,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum Kind {
|
pub enum Kind {
|
||||||
Report,
|
Report,
|
||||||
Filter,
|
Filter,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum Subsystem {
|
pub enum Subsystem {
|
||||||
SmtpIn,
|
SmtpIn,
|
||||||
SmtpOut,
|
SmtpOut,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
LinkAuth,
|
LinkAuth,
|
||||||
LinkConnect,
|
LinkConnect,
|
||||||
|
@ -114,16 +115,17 @@ pub enum Entry {
|
||||||
V1Filter(V1Filter),
|
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 {
|
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 {
|
pub fn get_event(&self) -> Event {
|
||||||
match self {
|
match self {
|
||||||
Entry::V1Report(r) => r.event.to_owned(),
|
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, _) = separator(input)?;
|
||||||
let (input, session_id) = parse_session_id(input)?;
|
let (input, session_id) = parse_session_id(input)?;
|
||||||
let (input, params) = many0(parse_param)(input)?;
|
let (input, params) = many0(parse_param)(input)?;
|
||||||
let _ = line_ending(input)?;
|
let (input, _) = line_ending(input)?;
|
||||||
let report = V1Report {
|
let report = V1Report {
|
||||||
timestamp,
|
timestamp,
|
||||||
subsystem,
|
subsystem,
|
||||||
|
@ -266,7 +268,7 @@ fn parse_v1_filter(input: &str) -> IResult<&str, Entry> {
|
||||||
let (input, _) = separator(input)?;
|
let (input, _) = separator(input)?;
|
||||||
let (input, token) = parse_token(input)?;
|
let (input, token) = parse_token(input)?;
|
||||||
let (input, params) = many0(parse_param)(input)?;
|
let (input, params) = many0(parse_param)(input)?;
|
||||||
let _ = line_ending(input)?;
|
let (input, _) = line_ending(input)?;
|
||||||
let filter = V1Filter {
|
let filter = V1Filter {
|
||||||
timestamp,
|
timestamp,
|
||||||
subsystem,
|
subsystem,
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
use crate::entry::Entry;
|
|
||||||
use crate::event_handlers::EventHandler;
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
pub struct Error {
|
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 {
|
impl From<log::SetLoggerError> for Error {
|
||||||
fn from(error: log::SetLoggerError) -> Self {
|
fn from(error: log::SetLoggerError) -> Self {
|
||||||
Error::new(&format!("Logger error: {}", error))
|
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))
|
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
65
opensmtpd/src/handler.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,12 @@
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
pub enum Response {
|
use crate::entry::Entry;
|
||||||
None,
|
use crate::errors::Error;
|
||||||
|
|
||||||
|
pub trait FilterInput {
|
||||||
|
fn next(&mut self) -> Result<Entry, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod stdin;
|
||||||
|
pub use stdin::StdIn;
|
66
opensmtpd/src/input/stdin.rs
Normal file
66
opensmtpd/src/input/stdin.rs
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,160 +6,101 @@
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
|
|
||||||
mod entry;
|
|
||||||
mod errors;
|
mod errors;
|
||||||
mod event_handlers;
|
mod handler;
|
||||||
mod logger;
|
mod logger;
|
||||||
mod response;
|
|
||||||
mod session_handler;
|
|
||||||
|
|
||||||
use crate::session_handler::SessionHandler;
|
pub mod entry;
|
||||||
use log::{error, warn};
|
pub mod input;
|
||||||
use std::collections::HashMap;
|
pub mod output;
|
||||||
use std::io;
|
|
||||||
use std::str::FromStr;
|
use log;
|
||||||
use std::sync::mpsc;
|
use std::default::Default;
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
pub use crate::entry::{Entry, Event};
|
|
||||||
pub use crate::errors::Error;
|
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::logger::SmtpdLogger;
|
||||||
pub use crate::response::Response;
|
pub use opensmtpd_derive::report;
|
||||||
pub use opensmtpd_derive::{event, report};
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! handlers {
|
macro_rules! simple_filter {
|
||||||
( $( $x:expr ),* ) => {
|
($( $x:expr ),*) => {
|
||||||
{
|
opensmtpd::simple_filter_log_level!(log::Level::Info $(,$x)*);
|
||||||
let mut temp_vec = Vec::new();
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! simple_filter_log_level {
|
||||||
|
($log_level: expr, $( $x:expr ),*) => {
|
||||||
|
let mut handlers = Vec::new();
|
||||||
$(
|
$(
|
||||||
temp_vec.push(($x)());
|
handlers.push(($x)());
|
||||||
)*
|
)*;
|
||||||
temp_vec
|
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 Filter<I, O>
|
||||||
pub struct NoContext;
|
where
|
||||||
|
I: crate::input::FilterInput + Default,
|
||||||
#[derive(Default)]
|
O: crate::output::FilterOutput + Default,
|
||||||
pub struct SmtpIn<T> {
|
{
|
||||||
sessions: HashMap<u64, (mpsc::Sender<Entry>, thread::JoinHandle<()>)>,
|
input: I,
|
||||||
event_handlers: Vec<EventHandler<T>>,
|
output: O,
|
||||||
|
handlers: Vec<Handler>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Clone + Default + 'static> SmtpIn<T> {
|
impl<I, O> Default for Filter<I, O>
|
||||||
fn register_events(&self) {
|
where
|
||||||
let mut evts = Vec::new();
|
I: crate::input::FilterInput + Default,
|
||||||
for eh in self.event_handlers.iter() {
|
O: crate::output::FilterOutput + Default,
|
||||||
match eh.event {
|
{
|
||||||
MatchEvent::Evt(ref v) => {
|
fn default() -> Self {
|
||||||
for e in v.iter() {
|
Filter {
|
||||||
evts.push(e);
|
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.
|
impl<I, O> Filter<I, O>
|
||||||
/// Since EOF should not append, it is considered as an error.
|
where
|
||||||
fn read(&self) -> Result<String, Error> {
|
I: crate::input::FilterInput + Default,
|
||||||
let mut input = String::new();
|
O: crate::output::FilterOutput + Default,
|
||||||
let nb = io::stdin().read_line(&mut input)?;
|
{
|
||||||
match nb {
|
pub fn set_handlers(&mut self, handlers: &[Handler]) -> &mut Self {
|
||||||
0 => Err(Error::new("end of file")),
|
self.handlers = handlers.to_vec();
|
||||||
_ => Ok(input),
|
self
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
pub fn register_events(&mut self) -> &mut Self {
|
||||||
/// Dispatch the entry into its session's thread. If such thread does not
|
// TODO: use self.output to register events
|
||||||
/// 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();
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the infinite loop that will read and process input from stdin.
|
|
||||||
pub fn run(&mut self) {
|
pub fn run(&mut self) {
|
||||||
self.register_events();
|
|
||||||
loop {
|
loop {
|
||||||
let line = match self.read() {
|
match self.input.next() {
|
||||||
Ok(l) => l,
|
Ok(entry) => {
|
||||||
|
log::debug!("{:?}", entry);
|
||||||
|
for h in self.handlers.iter() {
|
||||||
|
match h.send(&entry, &mut self.output) {
|
||||||
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.graceful_exit_children();
|
log::warn!("Warning: {}", e);
|
||||||
error!("{}", e);
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Error: {}", e);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match self.dispatch(&line) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => warn!("{}", e),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
19
opensmtpd/src/output.rs
Normal file
19
opensmtpd/src/output.rs
Normal 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};
|
25
opensmtpd/src/output/null.rs
Normal file
25
opensmtpd/src/output/null.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
33
opensmtpd/src/output/stdout.rs
Normal file
33
opensmtpd/src/output/stdout.rs
Normal 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);
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Reference in a new issue