postfix-log-parser 0.2.0

高性能模块化Postfix日志解析器,经3.2GB生产数据验证,SMTPD事件100%准确率
Documentation
//! Trivial-rewrite地址重写模块
//!
//! 处理Postfix trivial-rewrite守护进程的事件,包括地址重写、域名解析和配置验证

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

/// Trivial-rewrite地址重写事件
///
/// 记录地址重写和解析组件的配置警告和域名冲突信息
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TrivialRewriteEvent {
    /// 事件发生时间戳(UTC时间)
    pub timestamp: DateTime<Utc>,

    /// 进程ID(可选)
    /// trivial-rewrite进程的系统标识符
    pub pid: Option<u32>,

    /// 事件类型(配置覆盖警告或域名配置警告)
    pub event_type: TrivialRewriteEventType,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum TrivialRewriteEventType {
    ConfigOverrideWarning {
        file_path: String,
        line_number: u32,
        parameter_name: String,
        parameter_value: String,
    },
    DomainConfigWarning {
        domain: String,
        domain_list1: String, // e.g., "virtual_alias_domains"
        domain_list2: String, // e.g., "relay_domains"
        message: String,
    },
}

impl TrivialRewriteEvent {
    pub fn new(
        timestamp: DateTime<Utc>,
        pid: Option<u32>,
        event_type: TrivialRewriteEventType,
    ) -> Self {
        Self {
            timestamp,
            pid,
            event_type,
        }
    }

    pub fn config_override_warning(
        timestamp: DateTime<Utc>,
        pid: Option<u32>,
        file_path: String,
        line_number: u32,
        parameter_name: String,
        parameter_value: String,
    ) -> Self {
        Self::new(
            timestamp,
            pid,
            TrivialRewriteEventType::ConfigOverrideWarning {
                file_path,
                line_number,
                parameter_name,
                parameter_value,
            },
        )
    }

    pub fn domain_config_warning(
        timestamp: DateTime<Utc>,
        pid: Option<u32>,
        domain: String,
        domain_list1: String,
        domain_list2: String,
        message: String,
    ) -> Self {
        Self::new(
            timestamp,
            pid,
            TrivialRewriteEventType::DomainConfigWarning {
                domain,
                domain_list1,
                domain_list2,
                message,
            },
        )
    }

    /// Get a human-readable description of the event
    pub fn description(&self) -> String {
        match &self.event_type {
            TrivialRewriteEventType::ConfigOverrideWarning {
                file_path,
                line_number,
                parameter_name,
                parameter_value,
            } => {
                format!(
                    "Config override in {}:{} - parameter '{}' set to '{}'",
                    file_path, line_number, parameter_name, parameter_value
                )
            }
            TrivialRewriteEventType::DomainConfigWarning {
                domain,
                domain_list1,
                domain_list2,
                message,
            } => {
                format!(
                    "Domain config warning for '{}': {} (conflicts between {} and {})",
                    domain, message, domain_list1, domain_list2
                )
            }
        }
    }

    /// Get event severity for monitoring/alerting
    pub fn severity(&self) -> &'static str {
        match &self.event_type {
            TrivialRewriteEventType::ConfigOverrideWarning { .. } => "warning",
            TrivialRewriteEventType::DomainConfigWarning { .. } => "warning",
        }
    }

    /// Get the configuration parameter name if applicable
    pub fn parameter_name(&self) -> Option<&str> {
        match &self.event_type {
            TrivialRewriteEventType::ConfigOverrideWarning { parameter_name, .. } => {
                Some(parameter_name)
            }
            TrivialRewriteEventType::DomainConfigWarning { .. } => None,
        }
    }

    /// Get the domain name if applicable  
    pub fn domain(&self) -> Option<&str> {
        match &self.event_type {
            TrivialRewriteEventType::ConfigOverrideWarning { .. } => None,
            TrivialRewriteEventType::DomainConfigWarning { domain, .. } => Some(domain),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::TimeZone;

    #[test]
    fn test_config_override_warning_event() {
        let timestamp = Utc.with_ymd_and_hms(2024, 4, 8, 17, 54, 42).unwrap();
        let event = TrivialRewriteEvent::config_override_warning(
            timestamp,
            Some(81),
            "/etc/postfix/main.cf".to_string(),
            820,
            "smtpd_recipient_restrictions".to_string(),
            "check_client_access pcre:/etc/postfix/filter_trusted,permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination,pcre:/etc/postfix/filter_default".to_string(),
        );

        assert_eq!(event.timestamp, timestamp);
        assert_eq!(event.pid, Some(81));
        assert_eq!(event.severity(), "warning");
        assert_eq!(event.parameter_name(), Some("smtpd_recipient_restrictions"));
        assert!(event.description().contains("Config override"));
        assert!(event.description().contains("/etc/postfix/main.cf:820"));
    }

    #[test]
    fn test_domain_config_warning_event() {
        let timestamp = Utc.with_ymd_and_hms(2024, 4, 9, 14, 42, 34).unwrap();
        let event = TrivialRewriteEvent::domain_config_warning(
            timestamp,
            Some(81),
            "qq.com".to_string(),
            "virtual_alias_domains".to_string(),
            "relay_domains".to_string(),
            "do not list domain qq.com in BOTH virtual_alias_domains and relay_domains".to_string(),
        );

        assert_eq!(event.timestamp, timestamp);
        assert_eq!(event.pid, Some(81));
        assert_eq!(event.severity(), "warning");
        assert_eq!(event.domain(), Some("qq.com"));
        assert!(event.description().contains("Domain config warning"));
        assert!(event.description().contains("qq.com"));
        assert!(event
            .description()
            .contains("virtual_alias_domains and relay_domains"));
    }

    #[test]
    fn test_event_equality() {
        let timestamp = Utc.with_ymd_and_hms(2024, 4, 8, 17, 54, 42).unwrap();

        let event1 = TrivialRewriteEvent::config_override_warning(
            timestamp,
            Some(81),
            "/etc/postfix/main.cf".to_string(),
            820,
            "smtpd_recipient_restrictions".to_string(),
            "value1".to_string(),
        );

        let event2 = TrivialRewriteEvent::config_override_warning(
            timestamp,
            Some(81),
            "/etc/postfix/main.cf".to_string(),
            820,
            "smtpd_recipient_restrictions".to_string(),
            "value1".to_string(),
        );

        assert_eq!(event1, event2);
    }
}