This repository has been archived on 2023-09-20. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
rust-opensmtpd/opensmtpd/src/entry.rs
2019-09-18 20:43:46 +02:00

298 lines
8.3 KiB
Rust

// 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))
}