use crate::components::ComponentParser;
use crate::events::pickup::PickupEvent;
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 MAIL_PICKUP_REGEX: Regex = Regex::new(
r"^([A-F0-9]+): uid=(\d+) from=(.+)$"
).unwrap();
}
pub struct PickupParser;
impl PickupParser {
pub fn new() -> Self {
Self
}
fn parse_config_override_warning(&self, message: &str) -> Option<PickupEvent> {
if let Some(captures) = CONFIG_OVERRIDE_WARNING_REGEX.captures(message) {
let file_path = captures.get(1)?.as_str().to_string();
let line_number = captures.get(2)?.as_str().parse::<u32>().ok()?;
let parameter_name = captures.get(3)?.as_str().to_string();
let parameter_value = captures.get(4)?.as_str().to_string();
Some(PickupEvent::config_override_warning(
Utc::now(),
None,
file_path,
line_number,
parameter_name,
parameter_value,
))
} else {
None
}
}
fn parse_mail_pickup(&self, message: &str) -> Option<PickupEvent> {
if let Some(captures) = MAIL_PICKUP_REGEX.captures(message) {
let queue_id = captures.get(1)?.as_str().to_string();
let uid = captures.get(2)?.as_str().parse::<u32>().ok()?;
let sender = captures.get(3)?.as_str().to_string();
Some(PickupEvent::mail_pickup(
Utc::now(),
None,
queue_id,
uid,
sender,
))
} else {
None
}
}
}
impl ComponentParser for PickupParser {
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::Pickup(event));
}
if let Some(event) = self.parse_mail_pickup(clean_message) {
return Ok(ComponentEvent::Pickup(event));
}
Err(crate::error::ParseError::ComponentParseError {
component: "pickup".to_string(),
reason: format!("Unable to parse message: {}", message),
})
}
fn component_name(&self) -> &'static str {
"pickup"
}
}
impl Default for PickupParser {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::events::pickup::PickupEventType;
fn create_parser() -> PickupParser {
PickupParser::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 {
PickupEventType::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_mail_pickup() {
let parser = create_parser();
let message = "226751E20F00: uid=0 from=<root>";
let result = parser.parse_mail_pickup(message);
assert!(result.is_some());
let event = result.unwrap();
match &event.event_type {
PickupEventType::MailPickup {
queue_id,
uid,
sender,
} => {
assert_eq!(queue_id, "226751E20F00");
assert_eq!(*uid, 0);
assert_eq!(sender, "<root>");
}
_ => panic!("Expected MailPickup 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::Pickup(event) => {
assert_eq!(event.severity(), "warning");
assert_eq!(
event.parameter_name(),
Some("smtpd_client_message_rate_limit")
);
assert_eq!(event.queue_id(), None);
assert_eq!(event.sender(), None);
}
_ => panic!("Expected Pickup ComponentEvent"),
}
}
#[test]
fn test_component_parser_parse_mail_pickup() {
let parser = create_parser();
let message = "226751E20F00: uid=0 from=<root>";
let result = parser.parse(message);
assert!(result.is_ok());
match result.unwrap() {
ComponentEvent::Pickup(event) => {
assert_eq!(event.severity(), "info");
assert_eq!(event.parameter_name(), None);
assert_eq!(event.queue_id(), Some("226751E20F00"));
assert_eq!(event.sender(), Some("<root>"));
assert_eq!(event.uid(), Some(0));
}
_ => panic!("Expected Pickup 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, "pickup");
}
_ => panic!("Expected ComponentParseError"),
}
}
#[test]
fn test_component_name() {
let parser = create_parser();
assert_eq!(parser.component_name(), "pickup");
}
#[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 = "226751E20F00: uid=0 from=<root>";
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::Pickup(event) => {
assert_eq!(event.parameter_name(), Some("smtpd_discard_ehlo_keywords"));
}
_ => panic!("Expected Pickup ComponentEvent"),
}
}
}