postfix_log_parser/components/
trivial_rewrite.rs

1use crate::components::ComponentParser;
2use crate::events::trivial_rewrite::TrivialRewriteEvent;
3use crate::events::ComponentEvent;
4use chrono::Utc;
5use lazy_static::lazy_static;
6use regex::Regex;
7
8lazy_static! {
9    // 配置覆盖警告:/etc/postfix/main.cf, line 820: overriding earlier entry: smtpd_recipient_restrictions=check_client_access...
10    // 注意:主解析器已经移除了"warning:"前缀
11    static ref CONFIG_OVERRIDE_WARNING_REGEX: Regex = Regex::new(
12        r"^(.+?), line (\d+): overriding earlier entry: (.+?)=(.+)$"
13    ).unwrap();
14
15    // 域配置警告:do not list domain qq.com in BOTH virtual_alias_domains and relay_domains
16    // 注意:主解析器已经移除了"warning:"前缀
17    static ref DOMAIN_CONFIG_WARNING_REGEX: Regex = Regex::new(
18        r"^do not list domain (.+?) in BOTH (.+?) and (.+?)$"
19    ).unwrap();
20}
21
22pub struct TrivialRewriteParser;
23
24impl TrivialRewriteParser {
25    pub fn new() -> Self {
26        Self
27    }
28
29    /// 解析配置覆盖警告
30    /// 示例: "/etc/postfix/main.cf, line 820: overriding earlier entry: smtpd_recipient_restrictions=value"
31    /// 注意:主解析器已经移除了"warning:"前缀
32    fn parse_config_override_warning(&self, message: &str) -> Option<TrivialRewriteEvent> {
33        if let Some(captures) = CONFIG_OVERRIDE_WARNING_REGEX.captures(message) {
34            let file_path = captures.get(1)?.as_str().to_string();
35            let line_number: u32 = captures.get(2)?.as_str().parse().ok()?;
36            let parameter_name = captures.get(3)?.as_str().to_string();
37            let parameter_value = captures.get(4)?.as_str().to_string();
38
39            Some(TrivialRewriteEvent::config_override_warning(
40                Utc::now(),
41                None,
42                file_path,
43                line_number,
44                parameter_name,
45                parameter_value,
46            ))
47        } else {
48            None
49        }
50    }
51
52    /// 解析域配置警告
53    /// 示例: "do not list domain qq.com in BOTH virtual_alias_domains and relay_domains"
54    /// 注意:主解析器已经移除了"warning:"前缀
55    fn parse_domain_config_warning(&self, message: &str) -> Option<TrivialRewriteEvent> {
56        if let Some(captures) = DOMAIN_CONFIG_WARNING_REGEX.captures(message) {
57            let domain = captures.get(1)?.as_str().to_string();
58            let domain_list1 = captures.get(2)?.as_str().to_string();
59            let domain_list2 = captures.get(3)?.as_str().to_string();
60            let full_message = message.to_string(); // 不再需要移除"warning:"前缀
61
62            Some(TrivialRewriteEvent::domain_config_warning(
63                Utc::now(),
64                None,
65                domain,
66                domain_list1,
67                domain_list2,
68                full_message,
69            ))
70        } else {
71            None
72        }
73    }
74}
75
76impl ComponentParser for TrivialRewriteParser {
77    fn parse(&self, message: &str) -> Result<ComponentEvent, crate::error::ParseError> {
78        let clean_message = message.trim();
79
80        // 首先尝试解析配置覆盖警告
81        if let Some(event) = self.parse_config_override_warning(clean_message) {
82            return Ok(ComponentEvent::TrivialRewrite(event));
83        }
84
85        // 然后尝试解析域配置警告
86        if let Some(event) = self.parse_domain_config_warning(clean_message) {
87            return Ok(ComponentEvent::TrivialRewrite(event));
88        }
89
90        Err(crate::error::ParseError::ComponentParseError {
91            component: "trivial-rewrite".to_string(),
92            reason: format!("Unable to parse message: {}", message),
93        })
94    }
95
96    fn component_name(&self) -> &'static str {
97        "trivial-rewrite"
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use crate::events::trivial_rewrite::TrivialRewriteEventType;
105
106    fn create_parser() -> TrivialRewriteParser {
107        TrivialRewriteParser::new()
108    }
109
110    #[test]
111    fn test_parse_config_override_warning() {
112        let parser = create_parser();
113
114        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";
115        let result = parser.parse_config_override_warning(message);
116
117        assert!(result.is_some());
118        let event = result.unwrap();
119
120        match &event.event_type {
121            TrivialRewriteEventType::ConfigOverrideWarning {
122                file_path,
123                line_number,
124                parameter_name,
125                parameter_value,
126            } => {
127                assert_eq!(file_path, "/etc/postfix/main.cf");
128                assert_eq!(*line_number, 820);
129                assert_eq!(parameter_name, "smtpd_recipient_restrictions");
130                assert!(parameter_value.contains("check_client_access"));
131            }
132            _ => panic!("Expected ConfigOverrideWarning event type"),
133        }
134    }
135
136    #[test]
137    fn test_parse_domain_config_warning() {
138        let parser = create_parser();
139
140        let message = "do not list domain qq.com in BOTH virtual_alias_domains and relay_domains";
141        let result = parser.parse_domain_config_warning(message);
142
143        assert!(result.is_some());
144        let event = result.unwrap();
145
146        match &event.event_type {
147            TrivialRewriteEventType::DomainConfigWarning {
148                domain,
149                domain_list1,
150                domain_list2,
151                message: full_message,
152            } => {
153                assert_eq!(domain, "qq.com");
154                assert_eq!(domain_list1, "virtual_alias_domains");
155                assert_eq!(domain_list2, "relay_domains");
156                assert_eq!(
157                    full_message,
158                    "do not list domain qq.com in BOTH virtual_alias_domains and relay_domains"
159                );
160            }
161            _ => panic!("Expected DomainConfigWarning event type"),
162        }
163    }
164
165    #[test]
166    fn test_component_parser_parse_config_override() {
167        let parser = create_parser();
168
169        let message = "/etc/postfix/main.cf, line 806: overriding earlier entry: smtpd_client_message_rate_limit=0";
170        let result = parser.parse(message);
171
172        assert!(result.is_ok());
173        match result.unwrap() {
174            ComponentEvent::TrivialRewrite(event) => {
175                assert_eq!(event.severity(), "warning");
176                assert_eq!(
177                    event.parameter_name(),
178                    Some("smtpd_client_message_rate_limit")
179                );
180            }
181            _ => panic!("Expected TrivialRewrite ComponentEvent"),
182        }
183    }
184
185    #[test]
186    fn test_component_parser_parse_domain_config() {
187        let parser = create_parser();
188
189        let message = "do not list domain qq.com in BOTH virtual_alias_domains and relay_domains";
190        let result = parser.parse(message);
191
192        assert!(result.is_ok());
193        match result.unwrap() {
194            ComponentEvent::TrivialRewrite(event) => {
195                assert_eq!(event.severity(), "warning");
196                assert_eq!(event.domain(), Some("qq.com"));
197            }
198            _ => panic!("Expected TrivialRewrite ComponentEvent"),
199        }
200    }
201
202    #[test]
203    fn test_component_parser_parse_invalid() {
204        let parser = create_parser();
205
206        let message = "some unknown message format";
207        let result = parser.parse(message);
208
209        assert!(result.is_err());
210        match result.unwrap_err() {
211            crate::error::ParseError::ComponentParseError { component, .. } => {
212                assert_eq!(component, "trivial-rewrite");
213            }
214            _ => panic!("Expected ComponentParseError"),
215        }
216    }
217
218    #[test]
219    fn test_component_name() {
220        let parser = create_parser();
221        assert_eq!(parser.component_name(), "trivial-rewrite");
222    }
223
224    #[test]
225    fn test_parse_real_log_samples() {
226        let parser = create_parser();
227
228        // 真实日志样例1:配置覆盖
229        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";
230        let result1 = parser.parse(message1);
231        assert!(result1.is_ok());
232
233        // 真实日志样例2:域配置警告
234        let message2 = "do not list domain qq.com in BOTH virtual_alias_domains and relay_domains";
235        let result2 = parser.parse(message2);
236        assert!(result2.is_ok());
237
238        // 真实日志样例3:另一个参数覆盖
239        let message3 = "/etc/postfix/main.cf, line 826: overriding earlier entry: smtpd_discard_ehlo_keywords=silent-discard,dsn,etrn";
240        let result3 = parser.parse(message3);
241        assert!(result3.is_ok());
242
243        match result3.unwrap() {
244            ComponentEvent::TrivialRewrite(event) => {
245                assert_eq!(event.parameter_name(), Some("smtpd_discard_ehlo_keywords"));
246            }
247            _ => panic!("Expected TrivialRewrite ComponentEvent"),
248        }
249    }
250}