use crate::components::ComponentParser;
use crate::events::trivial_rewrite::TrivialRewriteEvent;
use crate::events::ComponentEvent;
use chrono::Utc;
use lazy_static::lazy_static;
use regex::Regex;
lazy_static! {
static ref CONFIG_OVERRIDE_WARNING_REGEX: Regex = Regex::new(
r"^(.+?), line (\d+): overriding earlier entry: (.+?)=(.+)$"
).unwrap();
static ref DOMAIN_CONFIG_WARNING_REGEX: Regex = Regex::new(
r"^do not list domain (.+?) in BOTH (.+?) and (.+?)$"
).unwrap();
}
pub struct TrivialRewriteParser;
impl TrivialRewriteParser {
pub fn new() -> Self {
Self
}
fn parse_config_override_warning(&self, message: &str) -> Option<TrivialRewriteEvent> {
if let Some(captures) = CONFIG_OVERRIDE_WARNING_REGEX.captures(message) {
let file_path = captures.get(1)?.as_str().to_string();
let line_number: u32 = captures.get(2)?.as_str().parse().ok()?;
let parameter_name = captures.get(3)?.as_str().to_string();
let parameter_value = captures.get(4)?.as_str().to_string();
Some(TrivialRewriteEvent::config_override_warning(
Utc::now(),
None,
file_path,
line_number,
parameter_name,
parameter_value,
))
} else {
None
}
}
fn parse_domain_config_warning(&self, message: &str) -> Option<TrivialRewriteEvent> {
if let Some(captures) = DOMAIN_CONFIG_WARNING_REGEX.captures(message) {
let domain = captures.get(1)?.as_str().to_string();
let domain_list1 = captures.get(2)?.as_str().to_string();
let domain_list2 = captures.get(3)?.as_str().to_string();
let full_message = message.to_string();
Some(TrivialRewriteEvent::domain_config_warning(
Utc::now(),
None,
domain,
domain_list1,
domain_list2,
full_message,
))
} else {
None
}
}
}
impl ComponentParser for TrivialRewriteParser {
fn parse(&self, message: &str) -> Result<ComponentEvent, crate::error::ParseError> {
let clean_message = message.trim();
if let Some(event) = self.parse_config_override_warning(clean_message) {
return Ok(ComponentEvent::TrivialRewrite(event));
}
if let Some(event) = self.parse_domain_config_warning(clean_message) {
return Ok(ComponentEvent::TrivialRewrite(event));
}
Err(crate::error::ParseError::ComponentParseError {
component: "trivial-rewrite".to_string(),
reason: format!("Unable to parse message: {}", message),
})
}
fn component_name(&self) -> &'static str {
"trivial-rewrite"
}
}
impl Default for TrivialRewriteParser {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::events::trivial_rewrite::TrivialRewriteEventType;
fn create_parser() -> TrivialRewriteParser {
TrivialRewriteParser::new()
}
#[test]
fn test_parse_config_override_warning() {
let parser = create_parser();
let message = "/etc/postfix/main.cf, line 820: overriding earlier entry: smtpd_recipient_restrictions=check_client_access pcre:/etc/postfix/filter_trusted,permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination,pcre:/etc/postfix/filter_default";
let result = parser.parse_config_override_warning(message);
assert!(result.is_some());
let event = result.unwrap();
match &event.event_type {
TrivialRewriteEventType::ConfigOverrideWarning {
file_path,
line_number,
parameter_name,
parameter_value,
} => {
assert_eq!(file_path, "/etc/postfix/main.cf");
assert_eq!(*line_number, 820);
assert_eq!(parameter_name, "smtpd_recipient_restrictions");
assert!(parameter_value.contains("check_client_access"));
}
_ => panic!("Expected ConfigOverrideWarning event type"),
}
}
#[test]
fn test_parse_domain_config_warning() {
let parser = create_parser();
let message = "do not list domain qq.com in BOTH virtual_alias_domains and relay_domains";
let result = parser.parse_domain_config_warning(message);
assert!(result.is_some());
let event = result.unwrap();
match &event.event_type {
TrivialRewriteEventType::DomainConfigWarning {
domain,
domain_list1,
domain_list2,
message: full_message,
} => {
assert_eq!(domain, "qq.com");
assert_eq!(domain_list1, "virtual_alias_domains");
assert_eq!(domain_list2, "relay_domains");
assert_eq!(
full_message,
"do not list domain qq.com in BOTH virtual_alias_domains and relay_domains"
);
}
_ => panic!("Expected DomainConfigWarning event type"),
}
}
#[test]
fn test_component_parser_parse_config_override() {
let parser = create_parser();
let message = "/etc/postfix/main.cf, line 806: overriding earlier entry: smtpd_client_message_rate_limit=0";
let result = parser.parse(message);
assert!(result.is_ok());
match result.unwrap() {
ComponentEvent::TrivialRewrite(event) => {
assert_eq!(event.severity(), "warning");
assert_eq!(
event.parameter_name(),
Some("smtpd_client_message_rate_limit")
);
}
_ => panic!("Expected TrivialRewrite ComponentEvent"),
}
}
#[test]
fn test_component_parser_parse_domain_config() {
let parser = create_parser();
let message = "do not list domain qq.com in BOTH virtual_alias_domains and relay_domains";
let result = parser.parse(message);
assert!(result.is_ok());
match result.unwrap() {
ComponentEvent::TrivialRewrite(event) => {
assert_eq!(event.severity(), "warning");
assert_eq!(event.domain(), Some("qq.com"));
}
_ => panic!("Expected TrivialRewrite ComponentEvent"),
}
}
#[test]
fn test_component_parser_parse_invalid() {
let parser = create_parser();
let message = "some unknown message format";
let result = parser.parse(message);
assert!(result.is_err());
match result.unwrap_err() {
crate::error::ParseError::ComponentParseError { component, .. } => {
assert_eq!(component, "trivial-rewrite");
}
_ => panic!("Expected ComponentParseError"),
}
}
#[test]
fn test_component_name() {
let parser = create_parser();
assert_eq!(parser.component_name(), "trivial-rewrite");
}
#[test]
fn test_parse_real_log_samples() {
let parser = create_parser();
let message1 = "/etc/postfix/main.cf, line 819: overriding earlier entry: smtpd_recipient_restrictions=check_client_access pcre:/etc/postfix/filter_trusted,permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination,pcre:/etc/postfix/filter_default";
let result1 = parser.parse(message1);
assert!(result1.is_ok());
let message2 = "do not list domain qq.com in BOTH virtual_alias_domains and relay_domains";
let result2 = parser.parse(message2);
assert!(result2.is_ok());
let message3 = "/etc/postfix/main.cf, line 826: overriding earlier entry: smtpd_discard_ehlo_keywords=silent-discard,dsn,etrn";
let result3 = parser.parse(message3);
assert!(result3.is_ok());
match result3.unwrap() {
ComponentEvent::TrivialRewrite(event) => {
assert_eq!(event.parameter_name(), Some("smtpd_discard_ehlo_keywords"));
}
_ => panic!("Expected TrivialRewrite ComponentEvent"),
}
}
}