mail_auth/arc/
parse.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use super::{ChainValidation, Results, Seal, Signature};
8use crate::common::parse::*;
9use crate::{
10    Error,
11    common::{crypto::Algorithm, parse::TagParser},
12    dkim::{Canonicalization, parse::SignatureParser},
13};
14use mail_parser::decoders::base64::base64_decode_stream;
15
16pub(crate) const CV: u64 = (b'c' as u64) | ((b'v' as u64) << 8);
17
18impl Signature {
19    #[allow(clippy::while_let_on_iterator)]
20    pub fn parse(header: &'_ [u8]) -> crate::Result<Self> {
21        let mut signature = Signature {
22            a: Algorithm::RsaSha256,
23            d: "".into(),
24            s: "".into(),
25            b: Vec::with_capacity(0),
26            bh: Vec::with_capacity(0),
27            h: Vec::with_capacity(0),
28            z: Vec::with_capacity(0),
29            l: 0,
30            x: 0,
31            t: 0,
32            i: 0,
33            ch: Canonicalization::Simple,
34            cb: Canonicalization::Simple,
35        };
36        let header_len = header.len();
37        let mut header = header.iter();
38
39        while let Some(key) = header.key() {
40            match key {
41                I => {
42                    signature.i = header.number().unwrap_or(0) as u32;
43                    if !(1..=50).contains(&signature.i) {
44                        return Err(Error::ArcInvalidInstance(signature.i));
45                    }
46                }
47                A => {
48                    signature.a = header.algorithm()?;
49                }
50                B => {
51                    signature.b =
52                        base64_decode_stream(&mut header, header_len, b';').ok_or(Error::Base64)?
53                }
54                BH => {
55                    signature.bh =
56                        base64_decode_stream(&mut header, header_len, b';').ok_or(Error::Base64)?
57                }
58                C => {
59                    let (ch, cb) = header.canonicalization(Canonicalization::Simple)?;
60                    signature.ch = ch;
61                    signature.cb = cb;
62                }
63                D => signature.d = header.text(true),
64                H => signature.h = header.items(),
65                L => signature.l = header.number().unwrap_or(0),
66                S => signature.s = header.text(true),
67                T => signature.t = header.number().unwrap_or(0),
68                X => signature.x = header.number().unwrap_or(0),
69                Z => signature.z = header.headers_qp(),
70                _ => header.ignore(),
71            }
72        }
73
74        if !signature.d.is_empty()
75            && !signature.s.is_empty()
76            && !signature.b.is_empty()
77            && !signature.bh.is_empty()
78            && !signature.h.is_empty()
79        {
80            Ok(signature)
81        } else {
82            Err(Error::MissingParameters)
83        }
84    }
85}
86
87impl Seal {
88    #[allow(clippy::while_let_on_iterator)]
89    pub fn parse(header: &'_ [u8]) -> crate::Result<Self> {
90        let mut seal = Seal {
91            a: Algorithm::RsaSha256,
92            d: "".into(),
93            s: "".into(),
94            b: Vec::with_capacity(0),
95            t: 0,
96            i: 0,
97            cv: ChainValidation::None,
98        };
99        let header_len = header.len();
100        let mut header = header.iter();
101        let mut cv = None;
102
103        while let Some(key) = header.key() {
104            match key {
105                I => {
106                    seal.i = header.number().unwrap_or(0) as u32;
107                }
108                A => {
109                    seal.a = header.algorithm()?;
110                }
111                B => {
112                    seal.b =
113                        base64_decode_stream(&mut header, header_len, b';').ok_or(Error::Base64)?
114                }
115                D => seal.d = header.text(true),
116                S => seal.s = header.text(true),
117                T => seal.t = header.number().unwrap_or(0),
118                CV => {
119                    match header.next_skip_whitespaces().unwrap_or(0) {
120                        b'n' | b'N' if header.match_bytes(b"one") => {
121                            cv = ChainValidation::None.into();
122                        }
123                        b'f' | b'F' if header.match_bytes(b"ail") => {
124                            cv = ChainValidation::Fail.into();
125                        }
126                        b'p' | b'P' if header.match_bytes(b"ass") => {
127                            cv = ChainValidation::Pass.into();
128                        }
129                        _ => return Err(Error::ArcInvalidCV),
130                    }
131                    if !header.seek_tag_end() {
132                        return Err(Error::ArcInvalidCV);
133                    }
134                }
135                H => {
136                    return Err(Error::ArcHasHeaderTag);
137                }
138                _ => header.ignore(),
139            }
140        }
141        seal.cv = cv.ok_or(Error::ArcInvalidCV)?;
142
143        if !(1..=50).contains(&seal.i) {
144            Err(Error::ArcInvalidInstance(seal.i))
145        } else if !seal.d.is_empty() && !seal.s.is_empty() && !seal.b.is_empty() {
146            Ok(seal)
147        } else {
148            Err(Error::MissingParameters)
149        }
150    }
151}
152
153impl Results {
154    #[allow(clippy::while_let_on_iterator)]
155    pub fn parse(header: &'_ [u8]) -> crate::Result<Self> {
156        let mut results = Results { i: 0 };
157        let mut header = header.iter();
158
159        while let Some(key) = header.key() {
160            match key {
161                I => {
162                    results.i = header.number().unwrap_or(0) as u32;
163                    break;
164                }
165                _ => header.ignore(),
166            }
167        }
168
169        if (1..=50).contains(&results.i) {
170            Ok(results)
171        } else {
172            Err(Error::ArcInvalidInstance(results.i))
173        }
174    }
175}