postfix-log-parser 0.2.0

高性能模块化Postfix日志解析器,经3.2GB生产数据验证,SMTPD事件100%准确率
Documentation
//! Pickup邮件拾取模块
//!
//! 处理Postfix pickup守护进程的事件,负责从maildrop目录拾取本地提交的邮件

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

/// Pickup邮件拾取事件
///
/// 记录pickup守护进程从maildrop目录拾取邮件的活动
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PickupEvent {
    /// 事件发生时间戳(UTC时间)
    pub timestamp: DateTime<Utc>,

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

    /// 事件类型(配置警告或邮件拾取)
    pub event_type: PickupEventType,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum PickupEventType {
    /// 配置覆盖警告
    ConfigOverrideWarning {
        file_path: String,
        line_number: u32,
        parameter_name: String,
        parameter_value: String,
    },
    /// 邮件拾取事件
    MailPickup {
        queue_id: String,
        uid: u32,
        sender: String,
    },
}

impl PickupEvent {
    pub fn new(timestamp: DateTime<Utc>, pid: Option<u32>, event_type: PickupEventType) -> 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,
            PickupEventType::ConfigOverrideWarning {
                file_path,
                line_number,
                parameter_name,
                parameter_value,
            },
        )
    }

    /// 创建邮件拾取事件
    pub fn mail_pickup(
        timestamp: DateTime<Utc>,
        pid: Option<u32>,
        queue_id: String,
        uid: u32,
        sender: String,
    ) -> Self {
        Self::new(
            timestamp,
            pid,
            PickupEventType::MailPickup {
                queue_id,
                uid,
                sender,
            },
        )
    }

    /// 获取事件严重性
    pub fn severity(&self) -> &'static str {
        match &self.event_type {
            PickupEventType::ConfigOverrideWarning { .. } => "warning",
            PickupEventType::MailPickup { .. } => "info",
        }
    }

    /// 获取参数名称(仅配置覆盖警告)
    pub fn parameter_name(&self) -> Option<&str> {
        match &self.event_type {
            PickupEventType::ConfigOverrideWarning { parameter_name, .. } => Some(parameter_name),
            _ => None,
        }
    }

    /// 获取队列ID(仅邮件拾取事件)
    pub fn queue_id(&self) -> Option<&str> {
        match &self.event_type {
            PickupEventType::MailPickup { queue_id, .. } => Some(queue_id),
            _ => None,
        }
    }

    /// 获取发件人(仅邮件拾取事件)
    pub fn sender(&self) -> Option<&str> {
        match &self.event_type {
            PickupEventType::MailPickup { sender, .. } => Some(sender),
            _ => None,
        }
    }

    /// 获取UID(仅邮件拾取事件)
    pub fn uid(&self) -> Option<u32> {
        match &self.event_type {
            PickupEventType::MailPickup { uid, .. } => Some(*uid),
            _ => None,
        }
    }
}

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

    #[test]
    fn test_config_override_warning_event() {
        let timestamp = Utc::now();
        let event = PickupEvent::config_override_warning(
            timestamp,
            Some(76),
            "/etc/postfix/main.cf".to_string(),
            820,
            "smtpd_recipient_restrictions".to_string(),
            "check_client_access pcre:/etc/postfix/filter_trusted".to_string(),
        );

        assert_eq!(event.timestamp, timestamp);
        assert_eq!(event.pid, Some(76));
        assert_eq!(event.severity(), "warning");
        assert_eq!(event.parameter_name(), Some("smtpd_recipient_restrictions"));
        assert_eq!(event.queue_id(), None);
        assert_eq!(event.sender(), None);
        assert_eq!(event.uid(), None);

        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"),
        }
    }

    #[test]
    fn test_mail_pickup_event() {
        let timestamp = Utc::now();
        let event = PickupEvent::mail_pickup(
            timestamp,
            Some(76),
            "226751E20F00".to_string(),
            0,
            "<root>".to_string(),
        );

        assert_eq!(event.timestamp, timestamp);
        assert_eq!(event.pid, Some(76));
        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));

        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"),
        }
    }

    #[test]
    fn test_event_equality() {
        let timestamp = Utc::now();
        let event1 = PickupEvent::config_override_warning(
            timestamp,
            Some(76),
            "/etc/postfix/main.cf".to_string(),
            806,
            "smtpd_client_message_rate_limit".to_string(),
            "0".to_string(),
        );

        let event2 = PickupEvent::config_override_warning(
            timestamp,
            Some(76),
            "/etc/postfix/main.cf".to_string(),
            806,
            "smtpd_client_message_rate_limit".to_string(),
            "0".to_string(),
        );

        assert_eq!(event1, event2);
    }
}