msg_auth_status/parser/
auth_results.rs

1//! Parsing for Auth Results using Logos
2
3use logos::{Lexer, Logos};
4
5use crate::alloc_yes::AuthenticationResults;
6use crate::auth::{SmtpAuthResult, SmtpAuthResultCode};
7use crate::dkim::{DkimResult, DkimResultCode};
8use crate::error::AuthResultsError;
9use crate::iprev::{IpRevResult, IpRevResultCode};
10use crate::spf::{SpfResult, SpfResultCode};
11
12use crate::alloc_yes::UnknownResult;
13
14mod host_version;
15mod policy;
16mod ptypes;
17mod reason;
18mod unknown;
19mod version;
20
21use crate::parser::comment::{parse_comment, CommentToken};
22use host_version::{parse_host_version, HostVersionToken};
23use policy::{parse_policy, PolicyToken};
24use ptypes::{parse_ptype_properties, PtypeToken};
25use reason::{parse_reason, ReasonToken};
26use unknown::{parse_unknown, UnknownToken};
27use version::{parse_version, VersionToken};
28
29#[cfg(feature = "mail_parser")]
30use mail_parser::HeaderValue;
31
32impl<'hdr> TryFrom<AuthResultToken<'hdr>> for SmtpAuthResultCode {
33    type Error = AuthResultsError<'hdr>;
34
35    fn try_from(token: AuthResultToken<'hdr>) -> Result<Self, Self::Error> {
36        let res = match token {
37            AuthResultToken::NoneNone => Self::NoneSmtp,
38            AuthResultToken::Pass => Self::Pass,
39            AuthResultToken::Fail => Self::Fail,
40            AuthResultToken::TempError => Self::TempError,
41            AuthResultToken::PermError => Self::PermError,
42            _ => return Err(AuthResultsError::InvalidSmtpAuthResult("".to_string())),
43        };
44        Ok(res)
45    }
46}
47
48impl<'hdr> TryFrom<AuthResultToken<'hdr>> for DkimResultCode {
49    type Error = AuthResultsError<'hdr>;
50
51    fn try_from(token: AuthResultToken<'hdr>) -> Result<Self, Self::Error> {
52        let res = match token {
53            AuthResultToken::NoneNone => Self::NoneDkim,
54            AuthResultToken::Pass => Self::Pass,
55            AuthResultToken::Fail => Self::Fail,
56            AuthResultToken::Policy => Self::Policy,
57            AuthResultToken::Neutral => Self::Neutral,
58            AuthResultToken::TempError => Self::TempError,
59            AuthResultToken::PermError => Self::PermError,
60            _ => return Err(AuthResultsError::InvalidDkimResult("".to_string())),
61        };
62        Ok(res)
63    }
64}
65
66impl<'hdr> TryFrom<AuthResultToken<'hdr>> for SpfResultCode {
67    type Error = AuthResultsError<'hdr>;
68
69    fn try_from(token: AuthResultToken<'_>) -> Result<Self, Self::Error> {
70        let res = match token {
71            AuthResultToken::NoneNone => Self::NoneSpf,
72            AuthResultToken::Pass => Self::Pass,
73            AuthResultToken::Fail => Self::Fail,
74            AuthResultToken::SoftFail => Self::SoftFail,
75            AuthResultToken::Policy => Self::Policy,
76            AuthResultToken::Neutral => Self::Neutral,
77            AuthResultToken::TempError => Self::TempError,
78            AuthResultToken::PermError => Self::PermError,
79            _ => return Err(AuthResultsError::InvalidSpfResult("".to_string())),
80        };
81        Ok(res)
82    }
83}
84
85impl<'hdr> TryFrom<AuthResultToken<'hdr>> for IpRevResultCode {
86    type Error = AuthResultsError<'hdr>;
87
88    fn try_from(token: AuthResultToken<'_>) -> Result<Self, Self::Error> {
89        let res = match token {
90            AuthResultToken::Pass => Self::Pass,
91            AuthResultToken::Fail => Self::Fail,
92            AuthResultToken::TempError => Self::TempError,
93            AuthResultToken::PermError => Self::PermError,
94            _ => return Err(AuthResultsError::InvalidIpRevResult("".to_string())),
95        };
96        Ok(res)
97    }
98}
99
100#[derive(Debug, Logos)]
101#[logos(skip r"[ \t\r\n]+")]
102pub enum AuthResultToken<'hdr> {
103    #[token("auth", priority = 200)]
104    Auth,
105    #[token("dkim", priority = 200)]
106    Dkim,
107    #[token("spf", priority = 200)]
108    Spf,
109    #[token("iprev", priority = 200)]
110    IpRev,
111
112    #[token("/", priority = 200)]
113    ForwardSlash,
114
115    #[token("=", priority = 200)]
116    Equal,
117
118    #[token("none", priority = 100)]
119    NoneNone,
120    #[token("softfail", priority = 100)]
121    SoftFail,
122    #[token("fail", priority = 100)]
123    Fail,
124    #[token("neutral", priority = 100)]
125    Neutral,
126    #[token("pass", priority = 100)]
127    Pass,
128    #[token("temperror", priority = 100)]
129    TempError,
130    #[token("permerror", priority = 100)]
131    PermError,
132
133    #[token("reason", priority = 50)]
134    Reason,
135
136    #[token("policy", priority = 50)]
137    Policy,
138
139    #[token("(", priority = 200)]
140    CommentStart,
141
142    #[regex("[a-z-_]+", |lex| lex.slice(), priority = 10)]
143    OtherAlphaDash(&'hdr str),
144
145    SuperDumbPlaceholder(&'hdr str),
146}
147
148#[derive(Clone, Debug, PartialEq)]
149enum Stage {
150    WantHost,
151    SawHost,
152    WantIdentifier,
153
154    /// auth = ..
155    WantAuthEqual,
156    WantAuthResult,
157
158    /// spf = ...
159    WantSpfEqual,
160    WantSpfResult,
161
162    // dkim = ...
163    WantDkimEqual,
164    WantDkimResult,
165
166    // iprev = ...
167    WantIpRevEqual,
168    WantIpRevResult,
169}
170
171// State machine helpers
172impl Stage {
173    // Is the current stage expecting resultset status
174    fn is_cur_expect_resultset_want(&self) -> bool {
175        matches!(
176            self,
177            Self::WantAuthResult
178                | Self::WantSpfResult
179                | Self::WantDkimResult
180                | Self::WantIpRevResult
181        )
182    }
183    // Is the current stage expecting '=' equal for a result set
184    fn is_cur_expect_resultset_equal(&self) -> bool {
185        matches!(
186            self,
187            Self::WantAuthEqual | Self::WantSpfEqual | Self::WantDkimEqual | Self::WantIpRevEqual
188        )
189    }
190    // Reflect the relevant Result for given WantEqual
191    fn equal_to_result(&mut self) -> bool {
192        let new_stage = match self {
193            Stage::WantAuthEqual => Stage::WantAuthResult,
194            Stage::WantSpfEqual => Stage::WantSpfResult,
195            Stage::WantDkimEqual => Stage::WantDkimResult,
196            Stage::WantIpRevEqual => Stage::WantIpRevResult,
197            _ => return false,
198        };
199        *self = new_stage;
200        true
201    }
202}
203
204#[derive(Clone, Debug)]
205enum ParseCurrentResultChoice<'hdr> {
206    SmtpAuth(SmtpAuthResult<'hdr>),
207    Spf(SpfResult<'hdr>),
208    Dkim(DkimResult<'hdr>),
209    IpRev(IpRevResult<'hdr>),
210}
211
212impl<'hdr> ParseCurrentResultChoice<'hdr> {
213    fn set_reason(&mut self, reason: &'hdr str) {
214        if let ParseCurrentResultChoice::Dkim(ref mut dkim_res) = self {
215            dkim_res.reason = Some(reason);
216        }
217    }
218}
219
220#[derive(Clone, Debug, Default)]
221struct ParseCurrentResultCode<'hdr> {
222    result: Option<ParseCurrentResultChoice<'hdr>>,
223}
224
225fn assign_result_code<'hdr>(
226    token: AuthResultToken<'hdr>,
227    stage: Stage,
228    cur_res: &mut ParseCurrentResultCode<'hdr>,
229) -> Result<(), AuthResultsError<'hdr>> {
230    let mut new_res: ParseCurrentResultCode<'hdr> = ParseCurrentResultCode::default();
231
232    match stage {
233        Stage::WantAuthResult => {
234            let code = SmtpAuthResultCode::try_from(token)?;
235            let smtp_auth_result = SmtpAuthResult {
236                code,
237                ..Default::default()
238            };
239            new_res.result = Some(ParseCurrentResultChoice::SmtpAuth(smtp_auth_result));
240            *cur_res = new_res;
241            Ok(())
242        }
243        Stage::WantSpfResult => {
244            let code = SpfResultCode::try_from(token)?;
245            let spf_result = SpfResult {
246                code,
247                ..Default::default()
248            };
249            new_res.result = Some(ParseCurrentResultChoice::Spf(spf_result));
250            *cur_res = new_res;
251            Ok(())
252        }
253        Stage::WantDkimResult => {
254            let code = DkimResultCode::try_from(token)?;
255            let dkim_result = DkimResult {
256                code,
257                ..Default::default()
258            };
259            new_res.result = Some(ParseCurrentResultChoice::Dkim(dkim_result));
260            *cur_res = new_res;
261            Ok(())
262        }
263        Stage::WantIpRevResult => {
264            let code = IpRevResultCode::try_from(token)?;
265            let iprev_result = IpRevResult {
266                code,
267                ..Default::default()
268            };
269            new_res.result = Some(ParseCurrentResultChoice::IpRev(iprev_result));
270            *cur_res = new_res;
271            Ok(())
272        }
273        _ => Err(AuthResultsError::InvalidResultStage),
274    }
275}
276
277impl<'hdr> From<&'hdr HeaderValue<'hdr>> for AuthenticationResults<'hdr> {
278    fn from(hval: &'hdr HeaderValue<'hdr>) -> Self {
279        let mut res = Self {
280            raw: hval.as_text(),
281            ..Default::default()
282        };
283
284        let text = match hval.as_text() {
285            None => {
286                res.errors.push(AuthResultsError::NoHeader);
287                return res;
288            }
289            Some(text) => text,
290        };
291
292        let mut host_lexer = HostVersionToken::lexer(text);
293
294        let host = match parse_host_version(&mut host_lexer) {
295            Ok(host) => host,
296            Err(e) => {
297                res.errors.push(e);
298                return res;
299            }
300        };
301
302        let mut lexer: Lexer<'hdr, AuthResultToken<'hdr>> = host_lexer.morph();
303        res.host = Some(host);
304
305        let mut stage = Stage::WantIdentifier;
306        let mut cur_res = ParseCurrentResultCode::default();
307
308        let mut raw_part_start = 0;
309
310        while let Some(token) = lexer.next() {
311            match token {
312                Ok(AuthResultToken::NoneNone) if stage == Stage::WantIdentifier => {
313                    res.none_done = true;
314                }
315                Ok(AuthResultToken::Auth) if stage == Stage::WantIdentifier => {
316                    stage = Stage::WantAuthEqual;
317                    raw_part_start = lexer.span().start;
318                }
319                Ok(AuthResultToken::Spf) if stage == Stage::WantIdentifier => {
320                    stage = Stage::WantSpfEqual;
321                    raw_part_start = lexer.span().start;
322                }
323                Ok(AuthResultToken::Dkim) if stage == Stage::WantIdentifier => {
324                    stage = Stage::WantDkimEqual;
325                    raw_part_start = lexer.span().start;
326                }
327                Ok(AuthResultToken::IpRev) if stage == Stage::WantIdentifier => {
328                    stage = Stage::WantIpRevEqual;
329                    raw_part_start = lexer.span().start;
330                }
331                Ok(AuthResultToken::Equal) if stage.is_cur_expect_resultset_equal() => {
332                    stage.equal_to_result();
333                }
334                // TODO: This is not handled atm - it's cursed.
335                Ok(AuthResultToken::Policy) => {
336                    let mut policy_lexer: Lexer<'hdr, PolicyToken<'hdr>> = lexer.morph();
337                    let _policy = match parse_policy(&mut policy_lexer) {
338                        Ok(policy) => policy,
339                        Err(e) => {
340                            res.errors.push(e);
341                            break;
342                        }
343                    };
344                    lexer = policy_lexer.morph();
345                }
346                Ok(
347                    AuthResultToken::Pass
348                    | AuthResultToken::Fail
349                    | AuthResultToken::TempError
350                    | AuthResultToken::PermError
351                    | AuthResultToken::SoftFail
352                    | AuthResultToken::NoneNone
353                    | AuthResultToken::Neutral,
354                ) if stage.is_cur_expect_resultset_want() => {
355                    if let Err(e) = assign_result_code(
356                        token.expect("BUG: Matched err?!"),
357                        stage.clone(),
358                        &mut cur_res,
359                    ) {
360                        res.errors.push(e);
361                        break;
362                    }
363
364                    let lexer_end = lexer.span().end;
365                    let mut ptype_lexer = PtypeToken::lexer(lexer.remainder());
366
367                    let raw_part_end =
368                        match parse_ptype_properties(&mut ptype_lexer, &mut cur_res.result) {
369                            Err(e) => {
370                                res.errors.push(e);
371                                break;
372                            }
373                            Ok(raw_part_end) => raw_part_end,
374                        };
375
376                    lexer.bump(ptype_lexer.span().end);
377
378                    stage = Stage::WantIdentifier;
379                    match cur_res.result {
380                        Some(ParseCurrentResultChoice::Dkim(mut dkim_res)) => {
381                            dkim_res.raw =
382                                Some(&lexer.source()[raw_part_start..lexer_end + raw_part_end]);
383                            res.dkim_result.push(dkim_res)
384                        }
385                        Some(ParseCurrentResultChoice::IpRev(mut iprev_res)) => {
386                            iprev_res.raw =
387                                Some(&lexer.source()[raw_part_start..lexer_end + raw_part_end]);
388                            res.iprev_result.push(iprev_res)
389                        }
390                        Some(ParseCurrentResultChoice::Spf(mut spf_res)) => {
391                            spf_res.raw =
392                                Some(&lexer.source()[raw_part_start..lexer_end + raw_part_end]);
393                            res.spf_result.push(spf_res)
394                        }
395                        Some(ParseCurrentResultChoice::SmtpAuth(mut auth_res)) => {
396                            auth_res.raw =
397                                Some(&lexer.source()[raw_part_start..lexer_end + raw_part_end]);
398                            res.smtp_auth_result.push(auth_res)
399                        }
400                        _ => {
401                            res.errors
402                                .push(AuthResultsError::ParseCurrentPushNotImplemented);
403                            break;
404                        }
405                    }
406                    cur_res = ParseCurrentResultCode::default();
407                }
408                Ok(AuthResultToken::ForwardSlash) => {
409                    let mut version_lexer = VersionToken::lexer(lexer.remainder());
410
411                    // TODO: Care about version ?
412                    let _version_res = match parse_version(&mut version_lexer) {
413                        Ok(version) => version,
414                        Err(e) => {
415                            res.errors.push(e);
416                            break;
417                        }
418                    };
419                    lexer.bump(version_lexer.span().end);
420                }
421                Ok(AuthResultToken::CommentStart) => {
422                    let mut comment_lexer: Lexer<'hdr, CommentToken<'hdr>> = lexer.morph();
423                    match parse_comment(&mut comment_lexer) {
424                        Ok(_comment) => {} // TODO: keep comments?
425                        Err(e) => {
426                            res.errors.push(AuthResultsError::ParseComment(e));
427                            break;
428                        }
429                    }
430                    lexer = comment_lexer.morph();
431                }
432                // unknown/unsupported methods encontered, parse them as "unknown" until ";" (consuming it)
433                Ok(AuthResultToken::OtherAlphaDash(_)) if stage == Stage::WantIdentifier => {
434                    let start = lexer.span().start;
435                    let mut unknown_lexer: Lexer<'hdr, UnknownToken<'hdr>> = lexer.morph();
436                    match parse_unknown(&mut unknown_lexer) {
437                        Ok(raw_end) => {
438                            let raw_str = &unknown_lexer.source()[start..raw_end];
439                            res.unknown_result.push(UnknownResult { raw: raw_str });
440                        }
441                        Err(e) => {
442                            res.errors.push(e);
443                            break;
444                        }
445                    }
446                    lexer = unknown_lexer.morph();
447                }
448                // TODO: OtherAlphaDash for Stage::WantResult
449                _ => {
450                    let cut_slice = &lexer.source()[lexer.span().start..];
451                    let cut_span = &lexer.source()[lexer.span().start..lexer.span().end];
452
453                    let detail = crate::error::ParsingDetail {
454                        component: "parse_ptypes_properties",
455                        span_start: lexer.span().start,
456                        span_end: lexer.span().end,
457                        source: lexer.source(),
458                        clipped_span: cut_span,
459                        clipped_remaining: cut_slice,
460                    };
461
462                    res.errors.push(AuthResultsError::ParsingDetailed(detail));
463                    break;
464                }
465            }
466        }
467        res
468    }
469}