postfix_log_parser/components/
postmap.rs

1//! # Postmap 映射表管理组件解析器
2//!
3//! Postmap 是 Postfix 的映射表管理工具组件,负责:
4//! - 创建和维护 Berkeley DB 映射表
5//! - 处理别名、虚拟域和传输映射
6//! - 验证映射表文件的完整性
7//! - 提供映射表操作的错误诊断
8//!
9//! ## 核心功能
10//!
11//! - **映射表管理**: 创建、更新、验证各种映射表
12//! - **数据库操作**: Berkeley DB、Hash、CDB 格式支持
13//! - **文件访问**: 映射源文件的读取和验证
14//! - **错误诊断**: 详细的文件和命令错误报告
15//!
16//! ## 支持的事件类型
17//!
18//! - **文件错误**: 映射文件访问失败、权限问题
19//! - **命令错误**: 参数验证失败、命令格式错误
20//!
21//! ## 示例日志格式
22//!
23//! ```text
24//! # 文件错误
25//! fatal: open /etc/postfix/virtual: No such file or directory
26//! fatal: open /etc/postfix/transport/: No such file or directory
27//!
28//! # 命令错误
29//! fatal: specify -b -h or -m only with supported file types
30//! ```
31
32use super::ComponentParser;
33use crate::error::ParseError;
34use crate::events::base::BaseEvent;
35use crate::events::postmap::{ErrorType, PostmapDetails, PostmapEvent, PostmapEventType};
36use crate::events::ComponentEvent;
37
38use regex::Regex;
39use std::collections::HashMap;
40
41/// POSTMAP组件解析器
42///
43/// 基于Postfix POSTMAP工具的真实日志格式开发
44/// POSTMAP工具的特点:
45/// - 管理映射表和数据库
46/// - 文件访问错误处理
47/// - 命令参数验证
48/// - Berkeley DB操作
49pub struct PostmapParser {
50    // 文件错误模式
51    file_error_regex: Regex,
52
53    // 命令错误模式
54    command_error_regex: Regex,
55}
56
57impl PostmapParser {
58    pub fn new() -> Self {
59        Self {
60            // 文件错误正则表达式(MasterParser已移除fatal前缀)
61            file_error_regex: Regex::new(r"^open (.+?): (.+)$")
62                .expect("POSTMAP文件错误正则表达式编译失败"),
63
64            // 命令错误正则表达式(MasterParser已移除fatal前缀)
65            command_error_regex: Regex::new(r"^specify .+$")
66                .expect("POSTMAP命令错误正则表达式编译失败"),
67        }
68    }
69
70    /// 解析POSTMAP日志行
71    pub fn parse_line(&self, line: &str, base_event: BaseEvent) -> Option<PostmapEvent> {
72        // 尝试解析文件错误
73        if let Some(event) = self.parse_file_error(line, base_event.clone()) {
74            return Some(event);
75        }
76
77        // 尝试解析命令错误
78        if let Some(event) = self.parse_command_error(line, base_event) {
79            return Some(event);
80        }
81
82        None
83    }
84
85    fn parse_file_error(&self, line: &str, base_event: BaseEvent) -> Option<PostmapEvent> {
86        if let Some(caps) = self.file_error_regex.captures(line) {
87            let file_path = caps.get(1).unwrap().as_str().to_string();
88            let error_message = caps.get(2).unwrap().as_str().to_string();
89
90            // 确定错误类型
91            let error_type = if error_message.contains("No such file or directory") {
92                if file_path.ends_with('/') {
93                    ErrorType::DirectoryNotFound
94                } else {
95                    ErrorType::FileNotFound
96                }
97            } else {
98                ErrorType::FileNotFound
99            };
100
101            let details = PostmapDetails::new_file_error(error_type, file_path, error_message);
102
103            let event = PostmapEvent {
104                timestamp: base_event.timestamp,
105                process_id: base_event.process_id,
106                event_type: PostmapEventType::FileError,
107                details,
108                extensions: HashMap::new(),
109            };
110
111            return Some(event);
112        }
113
114        None
115    }
116
117    fn parse_command_error(&self, line: &str, base_event: BaseEvent) -> Option<PostmapEvent> {
118        if self.command_error_regex.is_match(line) {
119            let error_message = line.to_string();
120
121            // 检查是否为文件错误(避免重复解析)
122            if error_message.starts_with("open ") {
123                return None;
124            }
125
126            // 检查是否为命令参数错误
127            let command_args = if error_message.contains("specify -b -h or -m only with") {
128                Some("-b -h -m".to_string())
129            } else {
130                None
131            };
132
133            let details = PostmapDetails::new_command_error(error_message, command_args);
134
135            let event = PostmapEvent {
136                timestamp: base_event.timestamp,
137                process_id: base_event.process_id,
138                event_type: PostmapEventType::CommandError,
139                details,
140                extensions: HashMap::new(),
141            };
142
143            return Some(event);
144        }
145
146        None
147    }
148}
149
150impl ComponentParser for PostmapParser {
151    fn parse(&self, message: &str) -> Result<ComponentEvent, ParseError> {
152        // 创建基础事件信息(时间戳和进程ID会在MasterParser中设置)
153        let base_event = BaseEvent {
154            timestamp: chrono::Utc::now(), // 临时值,会被MasterParser覆盖
155            hostname: String::new(),       // 临时值,会被MasterParser覆盖
156            component: "postmap".to_string(),
157            process_id: 0, // 临时值,会被MasterParser覆盖
158            log_level: crate::events::base::PostfixLogLevel::Fatal, // POSTMAP错误通常是fatal级别
159            raw_message: message.to_string(),
160        };
161
162        if let Some(event) = self.parse_line(message, base_event) {
163            Ok(ComponentEvent::Postmap(event))
164        } else {
165            Err(ParseError::ComponentParseError {
166                component: "postmap".to_string(),
167                reason: format!("无法识别的POSTMAP消息格式: {}", message),
168            })
169        }
170    }
171
172    fn component_name(&self) -> &'static str {
173        "postmap"
174    }
175}
176
177impl Default for PostmapParser {
178    fn default() -> Self {
179        Self::new()
180    }
181}