samotop_parser_nom/
lib.rs

1/*!
2Aim: wrap rustyknife nom parser for samotop
3
4:warning: this brings **GPLv3** requirements of the rustyknife crate.
5
6*/
7
8use nom::{bytes::streaming::tag, Err};
9use rustyknife::{
10    rfc5321::mailbox,
11    rfc5321::Command,
12    rfc5321::ReversePath,
13    rfc5321::{ForwardPath, Path},
14    types::AddressLiteral,
15    types::DomainPart,
16};
17use samotop_core::smtp::{command::*, *};
18pub use samotop_core::smtp::{ParseError, ParseResult, Parser};
19use std::net::IpAddr;
20
21#[derive(Clone, Copy, Debug, Default)]
22pub struct SmtpParserNom;
23
24impl Parser<SmtpCommand> for SmtpParserNom {
25    fn parse(&self, input: &[u8], state: &SmtpContext) -> ParseResult<SmtpCommand> {
26        if input.is_empty() {
27            return Err(ParseError::Incomplete);
28        }
29        if let Some(mode) = state.session.mode {
30            return Err(ParseError::Mismatch(format!(
31                "NOM - not parsing cmd in {:?} mode",
32                mode
33            )));
34        }
35        match rustyknife::rfc5321::command::<rustyknife::behaviour::Intl>(input) {
36            Ok((i, cmd)) => Ok((i.len(), map_cmd(cmd))),
37            Err(e) => Err(map_error(e)),
38        }
39    }
40}
41
42impl SmtpParserNom {
43    pub fn forward_path(&self, input: &[u8]) -> ParseResult<SmtpPath> {
44        let len = input.len();
45        let (input, _) = tag("<")(input).map_err(map_error)?;
46        let (input, m) = mailbox::<rustyknife::behaviour::Intl>(input).map_err(map_error)?;
47        let (input, _) = tag(">")(input).map_err(map_error)?;
48        Ok((len - input.len(), map_path(Path(m, vec![]))))
49    }
50}
51
52fn map_error(e: Err<()>) -> ParseError {
53    match e {
54        Err::Incomplete(_) => ParseError::Incomplete,
55        Err::Error(()) => ParseError::Mismatch("nom recoverable error".into()),
56        Err::Failure(()) => ParseError::Failed("nom failure".into()),
57    }
58}
59fn map_cmd(cmd: Command) -> SmtpCommand {
60    match cmd {
61        Command::HELO(domain) => SmtpCommand::Helo(SmtpHelo {
62            verb: "HELO".to_owned(),
63            host: SmtpHost::Domain(domain.to_string()),
64        }),
65        Command::EHLO(host) => SmtpCommand::Helo(SmtpHelo {
66            verb: "EHLO".to_owned(),
67            host: map_host(host),
68        }),
69        Command::MAIL(path, params) => SmtpCommand::Mail(SmtpMail::Mail(
70            map_reverse_path(path),
71            params.into_iter().map(|p| p.to_string()).collect(),
72        )),
73        Command::RCPT(path, params) => SmtpCommand::Rcpt(SmtpRcpt(
74            map_forward_path(path),
75            params.into_iter().map(|p| p.to_string()).collect(),
76        )),
77        Command::DATA => SmtpCommand::Data,
78        Command::RSET => SmtpCommand::Rset,
79        Command::NOOP(param) => {
80            SmtpCommand::Noop(param.map(|s| vec![s.to_string()]).unwrap_or_default())
81        }
82        Command::QUIT => SmtpCommand::Quit,
83        Command::VRFY(param) => SmtpCommand::Vrfy(param.to_string()),
84        Command::EXPN(param) => SmtpCommand::Expn(param.to_string()),
85        Command::HELP(param) => {
86            SmtpCommand::Help(param.map(|s| vec![s.to_string()]).unwrap_or_default())
87        }
88    }
89}
90fn map_forward_path(path: ForwardPath) -> SmtpPath {
91    match path {
92        ForwardPath::Path(path) => map_path(path),
93        ForwardPath::PostMaster(None) => SmtpPath::Postmaster,
94        ForwardPath::PostMaster(Some(domain)) => SmtpPath::Mailbox {
95            name: "postmaster".to_string(),
96            host: SmtpHost::Domain(domain.to_string()),
97            relays: vec![],
98        },
99    }
100}
101fn map_reverse_path(path: ReversePath) -> SmtpPath {
102    match path {
103        ReversePath::Path(path) => map_path(path),
104        ReversePath::Null => SmtpPath::Null,
105    }
106}
107fn map_path(path: Path) -> SmtpPath {
108    let Path(mailbox, domains) = path;
109    let (local, domain) = mailbox.into_parts();
110    SmtpPath::Mailbox {
111        name: local.to_string(),
112        host: map_host(domain),
113        relays: domains
114            .into_iter()
115            .map(|d| SmtpHost::Domain(d.to_string()))
116            .collect(),
117    }
118}
119fn map_host(host: DomainPart) -> SmtpHost {
120    match host {
121        DomainPart::Domain(domain) => SmtpHost::Domain(domain.to_string()),
122        DomainPart::Address(AddressLiteral::IP(IpAddr::V4(ip))) => SmtpHost::Ipv4(ip),
123        DomainPart::Address(AddressLiteral::IP(IpAddr::V6(ip))) => SmtpHost::Ipv6(ip),
124        DomainPart::Address(AddressLiteral::Tagged(label, literal)) => {
125            SmtpHost::Other { label, literal }
126        }
127        DomainPart::Address(AddressLiteral::FreeForm(literal)) => SmtpHost::Invalid {
128            label: String::new(),
129            literal,
130        },
131    }
132}