Commit
+1601 -614 +/-22 browse
1 | diff --git a/resources/dkim/001.txt b/resources/dkim/001.txt |
2 | index 08dec3d..da9fb24 100644 |
3 | --- a/resources/dkim/001.txt |
4 | +++ b/resources/dkim/001.txt |
5 | @@ -1,5 +1,5 @@ |
6 | - brisbane v=DKIM1; k=ed25519; p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo= |
7 | - test v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkHlOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQiY/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqMKrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB |
8 | + brisbane._domainkey.football.example.com v=DKIM1; k=ed25519; p=11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo= |
9 | + test._domainkey.football.example.com v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkHlOQoBTzWRiGs5V6NpP3idY6Wk08a5qhdR6wy5bdOKb2jLQiY/J16JYi0Qvx/byYzCNb3W91y3FutACDfzwQ/BC/e/8uBsCR+yz1Lxj+PL6lHvqMKrM3rG4hstT5QjvHO9PzoxZyVYLzBfO2EeC3Ip3G+2kryOTIKT+l/K4w3QIDAQAB |
10 | |
11 | DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; |
12 | d=football.example.com; i=@football.example.com; |
13 | diff --git a/resources/dkim/002.txt b/resources/dkim/002.txt |
14 | index c73dd5a..6766d8a 100644 |
15 | --- a/resources/dkim/002.txt |
16 | +++ b/resources/dkim/002.txt |
17 | @@ -1,4 +1,4 @@ |
18 | - newengland v=DKIM1; p=MIGJAoGBALVI635dLK4cJJAH3Lx6upo3X/Lm1tQz3mezcWTA3BUBnyIsdnRf57aD5BtNmhPrYYDlWlzw3UgnKisIxktkk5+iMQMlFtAS10JB8L3YadXNJY+JBcbeSi5TgJe4WFzNgW95FWDAuSTRXSWZfA/8xjflbTLDx0euFZOM7C4T0GwLAgMBAAE= |
19 | + newengland._domainkey.example.com v=DKIM1; p=MIGJAoGBALVI635dLK4cJJAH3Lx6upo3X/Lm1tQz3mezcWTA3BUBnyIsdnRf57aD5BtNmhPrYYDlWlzw3UgnKisIxktkk5+iMQMlFtAS10JB8L3YadXNJY+JBcbeSi5TgJe4WFzNgW95FWDAuSTRXSWZfA/8xjflbTLDx0euFZOM7C4T0GwLAgMBAAE= |
20 | |
21 | DKIM-Signature: a=rsa-sha256; bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; |
22 | c=simple/simple; d=example.com; |
23 | diff --git a/resources/dkim/003.txt b/resources/dkim/003.txt |
24 | index 429b64f..9e87ac0 100644 |
25 | --- a/resources/dkim/003.txt |
26 | +++ b/resources/dkim/003.txt |
27 | @@ -1,4 +1,4 @@ |
28 | - ietf1 k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNzNnjKTd5cczd2CDzHflCZuv1tMWYwd7zE+deoJ6s/fXR7/n9ZIBnDS5egt7HAHjNjZrmjcoRlfSsNxRJvUQFyYvaU1BT1s8R+mkPgSOqZ4t9HqAVjiczn2B9+dbjdNN+S/zvSyMMuSCSJDKKAXhBpDeQTpeY7/UdP9s6ws0yjQIDAQAB |
29 | + ietf1._domainkey.ietf.org k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNzNnjKTd5cczd2CDzHflCZuv1tMWYwd7zE+deoJ6s/fXR7/n9ZIBnDS5egt7HAHjNjZrmjcoRlfSsNxRJvUQFyYvaU1BT1s8R+mkPgSOqZ4t9HqAVjiczn2B9+dbjdNN+S/zvSyMMuSCSJDKKAXhBpDeQTpeY7/UdP9s6ws0yjQIDAQAB |
30 | |
31 | DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ietf.org; s=ietf1; |
32 | t=1667592145; bh=M3BM66+ux2IbqyOhw6XrN0rYwgjbrSbsG7H+29IL9UQ=; |
33 | diff --git a/resources/dkim/004.txt b/resources/dkim/004.txt |
34 | new file mode 100644 |
35 | index 0000000..55ed848 |
36 | --- /dev/null |
37 | +++ b/resources/dkim/004.txt |
38 | @@ -0,0 +1,97 @@ |
39 | + s1024-2013-q3._domainkey.facebookmail.com k=rsa; t=s; h=sha256; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAUemE56fSDRo+H9Cu8u0uEIOXKON0YbB5A10wuWvNc7bUFIjL0tiUIzhktZjhAXc5CWw2/TZnTZaLZtmtJ2MRfd2e+ty7LylkRAiZUWaT3dcDGVVibWn27DIz3+oCnbL7CFiLzxCZnxHx8B7BC/UM7UCCJMrAgaJWJR6tYwz0MwIDAQAB |
40 | + |
41 | + DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=facebookmail.com; |
42 | + s=s1024-2013-q3; t=1667862801; |
43 | + bh=WD7cPh9RpkUGmkO18mzurJGvkR3KhuxeMfs8TP7zhXo=; |
44 | + h=Date:To:Subject:From:MIME-Version:Content-Type; |
45 | + b=gKG3clziu1BwjHb4J5MWWs9j40JK+uZ9zAZlXyTykDLBxK8+Mh0oN96q8LiL11I9a |
46 | + zhcUt/wP8Svjg2aaRnFfl1IQvrnmAMfsNQcRRRaiAOadBEVUfPwfFokUKjO5Q8/MmH |
47 | + JiK9cNz78PAB4X3MR/biV4phSSlRymGUkzmmK9ns= |
48 | + X-Facebook: from 2401:db00:116c:4513:face:0:1c0:0 ([MTI3LjAuMC4x]) |
49 | + by www.facebook.com with HTTPS (ZuckMail); |
50 | + Date: Mon, 7 Nov 2022 15:13:21 -0800 |
51 | + To: Mauro DG <mauro@minter.ltd> |
52 | + Subject: The new Pages experience is replacing classic Pages |
53 | + X-Priority: 3 |
54 | + X-Mailer: ZuckMail [version 1.00] |
55 | + From: "Facebook" <notification@facebookmail.com> |
56 | + Reply-to: noreply <noreply@facebookmail.com> |
57 | + Errors-To: notification@facebookmail.com |
58 | + X-Facebook-Notify: aymt_profile_plus_preemptive_transition_tip_notif:aymt_profile_plus_rollback_removal_email_tip; mailid=U1U5ece94830d9aeG29d41ac9G5ece991c6dc80G2dcf |
59 | + List-Unsubscribe: <https://facebook.com/aymt/unsubscribe/?c=510530525742901&n=1667862800817280&t=4503113996480200> |
60 | + Feedback-ID: 30:aymt_profile_plus_preemptive_transition_tip_notif:Facebook |
61 | + X-FACEBOOK-PRIORITY: 0 |
62 | + X-Auto-Response-Suppress: All |
63 | + MIME-Version: 1.0 |
64 | + Content-Transfer-Encoding: quoted-printable |
65 | + Content-Type: text/html; charset="UTF-8" |
66 | + Message-ID: <c578a5be-5ef1-11ed-8284-2b57b60ab540@facebookmail.com> |
67 | + |
68 | + <html><head><meta http-equiv=3D"Content-Type" content=3D"text/html; = |
69 | + charset=3DUTF-8" /><meta name=3D"viewport" = |
70 | + content=3D"width=3Ddevice-width, initial-scale=3D1.0" /><title>Facebook = |
71 | + for Business</title></head><body><table style=3D"background-color: = |
72 | + #ffffff" border=3D"0" cellpadding=3D"0" cellspacing=3D"0" = |
73 | + width=3D"100%"><tr><td align=3D"center"><!--[if (gte mso 9)|(IE)]> |
74 | + <table |
75 | + align=3D"center" |
76 | + border=3D"0" |
77 | + cellspacing=3D"0" |
78 | + cellpadding=3D"0" |
79 | + width=3D"720"> |
80 | + <tr> |
81 | + <td align=3D"center" valign=3D"top" width=3D"720"> |
82 | + <![endif]--><table align=3D"left" border=3D"0" = |
83 | + cellpadding=3D"0" cellspacing=3D"0" style=3D"max-width: 720px;" = |
84 | + width=3D"100%"><tr><td><div style=3D"display:none; font-size:1px; = |
85 | + color:#333333; line-height:1px; max-height:0px; max-width:0px; opacity:0; = |
86 | + overflow:hidden;"></div></td></tr><tr><td align=3D"left" = |
87 | + valign=3D"top"><table bgcolor=3D"#ffffff" style=3D"padding: 5px 0px; = |
88 | + width: 100%;"><tr><td><table><tr><td><img width=3D"32" height=3D"32" = |
89 | + src=3D"https://static.xx.fbcdn.net/rsrc.php/v3/yc/r/I92GqZOkKcu.png" = |
90 | + style=3D"border:0;max-width:100%;" alt=3D"Header" title=3D"Image" = |
91 | + /></td><td width=3D"2px"></td><td style=3D"font: 22px Helvetica, = |
92 | + Arial;"><span class=3D"mb_text" style=3D"font-family:Helvetica = |
93 | + Neue,Helvetica,Lucida Grande,tahoma,verdana,arial,sans-serif;font-size:16p= |
94 | + x;line-height:21px;font-weight:bold;color:#141823;">Switching back to = |
95 | + classic Pages will no longer be available for your = |
96 | + Page(s).</span></td></tr></table></td></tr></table></td></tr><tr><td><div = |
97 | + align=3D"center"><table cellpadding=3D"0" cellspacing=3D"0" = |
98 | + align=3D"center" style=3D"background-color: #e5e5e5; max-width: 100%; = |
99 | + padding: 0px"><tr><td align=3D"center" style=3D"background-color: #e5e5e5; = |
100 | + max-width: 100%; padding: 0px" height=3D"1px" = |
101 | + width=3D"720px"></td></tr></table></div></td></tr><tr><td align=3D"center" = |
102 | + style=3D"padding: 10px 0px 10px 0px; text-align:center; font: 16px = |
103 | + Helvetica, Arial; " valign=3D"top"><p style=3D"text-align:left; = |
104 | + color:#000000; font-size:14pt;">Mauro, </p><div style=3D"text-align:left; = |
105 | + margin-top: 20px">Starting soon, the option to switch back to the classic = |
106 | + Pages experience will no longer be available for your Page(s). Over the = |
107 | + coming months, all Pages will be updated to the new Pages experience and = |
108 | + the classic Pages experience will no longer be available.</div><div = |
109 | + style=3D"text-align:left;"><p style=3D"text-align:left;">Thanks,<br />The = |
110 | + Facebook Pages Team</p></div></td></tr><tr><td style=3D"padding: = |
111 | + 10px"></td></tr><tr><td><div align=3D"center"><table cellpadding=3D"0" = |
112 | + cellspacing=3D"0" align=3D"center" style=3D"background-color: #e5e5e5; = |
113 | + max-width: 100%; padding: 0px"><tr><td align=3D"center" = |
114 | + style=3D"background-color: #e5e5e5; max-width: 100%; padding: 0px" = |
115 | + height=3D"1px" width=3D"720px"></td></tr></table></div></td></tr><tr><td = |
116 | + style=3D"padding: 10px"></td></tr><tr><td><table align=3D"left" = |
117 | + style=3D""><tr><td align=3D"left"><div align=3D"left">This message was = |
118 | + sent to <a = |
119 | + style=3D"color:#1b74e4;text-decoration:none;">mauro@minter.ltd</a>. = |
120 | + If you don't want to receive these emails from Meta in the future, = |
121 | + please <a href=3D"https://facebook.com/aymt/unsubscribe/?c=3D5105305257429= |
122 | + 01&n=3D1667862800817280&t=3D4503113996480200" = |
123 | + style=3D"color:#1b74e4;text-decoration:none;">unsubscribe here</a>.<br = |
124 | + />Meta Platforms Ireland Ltd., Attention: Community Operations, 4 Grand = |
125 | + Canal Square, Dublin 2, = |
126 | + Ireland</div></td></tr></table></td></tr></table><!--[if (gte mso = |
127 | + 9)|(IE)]> |
128 | + </td> |
129 | + </tr> |
130 | + </table> |
131 | + <![endif]--></td></tr><tr><td><span style=3D""><img = |
132 | + src=3D"https://facebook.com/aymt/aa/?e=3D%7B%22c%22%3A%22f1czoo1iezle1%22%= |
133 | + 2C%22t%22%3A%224446%3D4z44_atc%22%2C%22n%22%3A%22z%3D9%3D06jfs9ca6%22%7D" = |
134 | + style=3D"border:0;width:1px;height:1px;" = |
135 | + /></span></td></tr></table></body></html> |
136 | diff --git a/resources/dkim/005.txt b/resources/dkim/005.txt |
137 | new file mode 100644 |
138 | index 0000000..16f8cd9 |
139 | --- /dev/null |
140 | +++ b/resources/dkim/005.txt |
141 | @@ -0,0 +1,123 @@ |
142 | + sysmsg-1._domainkey.topicbox.com v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC43V3fV5jD8p3isdDRIz5yXSg85VrZzd7fPCz3q5B2Z3Be/yOyumWmG4hX5UHn0HR/Im5cnzcwZeNu6SnlLJwggN0H664JHTjwQp8YiWKfEcDfOdz4K4kL6OrasDwb1nk5JBuJloGRpK5cTNEa0J0SNG+bhBzFfLvG5qp3p86RZwIDAQAB |
143 | + |
144 | + DKIM-Signature: v=1; a=rsa-sha256; c=relaxed; d=topicbox.com; h=from:to |
145 | + :subject:date:mime-version:content-type |
146 | + :content-transfer-encoding:message-id; s=sysmsg-1; t=1667843664; |
147 | + x=1667930064; bh=FuZLEu0Dc6ZvRmafp+d/dAFzxmaVkLWLgzk8S9wR6Ro=; b= |
148 | + sEM2Pfv1NVwlnGJ5t15xPJdipz4HLJPMMr92LzU2yTrrtcHxiATbyENUZlLwomca |
149 | + 8MLlGDtVvsVMqPqoTRUipuwICkCbMVnn0fqyv64YQcr74kwuv5pmgUyyHJpK7dzu |
150 | + BwdcpbBp7yBQ+vi5xwtcCEdsRqALNEhWrGAir6ExzrE= |
151 | + From: Topicbox <topicbox@topicbox.com> |
152 | + To: "Mauro D." <mauro@stalw.art> |
153 | + Subject: Your Topicbox login code: YKPMYE |
154 | + Date: Mon, 7 Nov 2022 12:54:24 -0500 |
155 | + MIME-Version: 1.0 |
156 | + Content-Type: multipart/alternative; boundary=16678436640.cBEEbcFd.41169 |
157 | + Content-Transfer-Encoding: 7bit |
158 | + Message-ID: <373C5876-5EC5-11ED-849C-F948292D11B0@lc.jmap.topicbox.com> |
159 | + Auto-Submitted: auto-generated |
160 | + |
161 | + |
162 | + --16678436640.cBEEbcFd.41169 |
163 | + Date: Mon, 7 Nov 2022 12:54:24 -0500 |
164 | + MIME-Version: 1.0 |
165 | + Content-Type: text/plain; charset=utf-8 |
166 | + Content-Transfer-Encoding: quoted-printable |
167 | + |
168 | + # Single-use login code for mauro@stalw.art is: |
169 | + |
170 | + ### YKPMYE |
171 | + |
172 | + |
173 | + |
174 | + --- |
175 | + |
176 | + **This code is valid for 10 minutes.** If more than 10 minutes have passed, |
177 | + please request a new code at [your organization=E2=80=99s login page](https= |
178 | + ://jmap.topicbox.com/login). |
179 | + |
180 | + |
181 | + |
182 | + |
183 | + --16678436640.cBEEbcFd.41169 |
184 | + Date: Mon, 7 Nov 2022 12:54:24 -0500 |
185 | + MIME-Version: 1.0 |
186 | + Content-Type: text/html; charset=utf-8 |
187 | + Content-Transfer-Encoding: quoted-printable |
188 | + |
189 | + <!DOCTYPE html> |
190 | + <html lang=3D"en" xmlns:v=3D"urn:schemas-microsoft-com:vml" xmlns:o=3D"urn:= |
191 | + schemas-microsoft-com:office:office" xmlns:w=3D"urn:schemas-microsoft-com:o= |
192 | + ffice:word"> |
193 | + <meta charset=3D"utf-8"> |
194 | + <meta name=3D"viewport" content=3D"width=3Ddevice-width"> |
195 | + <!--[if gte mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPer= |
196 | + Inch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]--> |
197 | + <style> |
198 | + h1,h2,h3,p,pre,ul,ol,hr{margin-top:0;margin-bottom:0;padding-bottom:20px} |
199 | + h1{font-size:25px;line-height:1.2;color:#078277} |
200 | + h2{font:inherit} |
201 | + .main h2 a{text-decoration:none;text-align:center;display:inline-block;bord= |
202 | + er:1px solid #007c67;border-color:rgba(0,89,74,.62745);border-radius:4px;pa= |
203 | + dding:12px 30px;line-height:1.4;background-color:#078277;background-image:l= |
204 | + inear-gradient(#4db5ac,#007c67);background-position:-1px -1px;background-si= |
205 | + ze:auto 104%;background-size:auto calc(100% + 2px);box-shadow:0 1px 1.5px r= |
206 | + gba(0,0,0,.25);color:#fff;font-weight:600} |
207 | + .button:active,.main h2 a:active{background-color:#078277;background-image:= |
208 | + linear-gradient(#45a59d,#006151);border-color:#006151;border-color:rgba(0,6= |
209 | + 1,51,.62745)} |
210 | + h3{text-align:center;font-size:40px;line-height:1.5} |
211 | + hr{border:0;border-top:1px solid #e0e2e3;height:0;background:transparent} |
212 | + .main a{color:#3f5db2} |
213 | + h1,h3,.main a,strong{font-weight:600} |
214 | + @media(max-width:500px){.main{padding:10px 10px 0 10px!important}.logo{padd= |
215 | + ing:10px!important} } |
216 | + @media(max-width:400px){.button,.main h2 a{display:block} } |
217 | + @media screen{ |
218 | + @import url("https://fonts.googleapis.com/css?family=3DSource+Sans+Pro:400,= |
219 | + 600"); |
220 | + } |
221 | + </style> |
222 | + <body bgcolor=3D"#ebeced" style=3D"padding:0;margin:0;background:#ebeced;co= |
223 | + lor:#333e48;font:16px/25px 'Source Sans Pro','Helvetica Neue',Arial,sans-se= |
224 | + rif;text-align:center;-webkit-text-size-adjust:none"> |
225 | + |
226 | + <header> |
227 | + <a href=3D"https://www.topicbox.com" class=3D"logo" style=3D"border:none;te= |
228 | + xt-decoration:none;display:inline-block;padding:15px;color:inherit"><img sr= |
229 | + c=3D"https://www.topicbox.com/_email-logo-v1-1x.png" srcset=3D"https://www.= |
230 | + topicbox.com/_email-logo-v1-2x.png 2x" width=3D"120" height=3D"30" alt=3D"T= |
231 | + opicbox" style=3D"font-size:23px;line-height:30px;font-weight:600;vertical-= |
232 | + align:top"></a> |
233 | + </header> |
234 | + |
235 | + <div style=3D"background:#fff;width:600px;max-width:100%;margin:auto;text-a= |
236 | + lign:left"> |
237 | + <div class=3D"main" style=3D"padding:40px 30px 30px"> |
238 | + <h1>Single-use login code for mauro@stalw.art is:</h1> |
239 | + |
240 | + <h3>YKPMYE</h3> |
241 | + |
242 | + <hr /> |
243 | + |
244 | + <p><strong>This code is valid for 10 minutes.</strong> If more than 10 minu= |
245 | + tes have passed, |
246 | + please request a new code at <a href=3D"https://jmap.topicbox.com/login">yo= |
247 | + ur organization’s login page</a>.</p> |
248 | + |
249 | + </div> |
250 | + <div style=3D"height:6px;background:linear-gradient(90deg,#009688,#3f5db2)"= |
251 | + ></div> |
252 | + </div> |
253 | + |
254 | + <footer> |
255 | + <p style=3D"padding:25px;line-height:20px;font-size:14px;color:#858b91"> |
256 | + <a href=3D"https://www.topicbox.help/hc/en-us" style=3D"padding:0 4px;color= |
257 | + :#858b91">Help</a> |
258 | + |
259 | + |
260 | + <br>Powered by <a href=3D"https://www.topicbox.com" style=3D"color:#858b91;= |
261 | + font-weight:600">Topicbox</a></p> |
262 | + </footer> |
263 | + |
264 | + --16678436640.cBEEbcFd.41169-- |
265 | diff --git a/resources/dkim/006.txt b/resources/dkim/006.txt |
266 | new file mode 100644 |
267 | index 0000000..cf82ac3 |
268 | --- /dev/null |
269 | +++ b/resources/dkim/006.txt |
270 | @@ -0,0 +1,404 @@ |
271 | + dk2016._domainkey.github.com v=DKIM1; h=sha256; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDn7EiK3r/vRRde/oD9XAsACz44UTrt2j+hGKdqQ093/QBbPZS99TKxBkcKeWEnu+TzV+WigS8eD424pZVNP2Y4Ta5qbWdtJa+jtoc9953m7WOkTYMM4/iiDxPzhg2yxWdxu3VvuyiZBLhPXzX54mj8rXaTyXXWry2+CRQqDds9pwIDAQAB; t=s |
272 | + |
273 | + DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; s=dk2016; d=github.com; |
274 | + h=Message-ID:List-Unsubscribe:From:Reply-To:To:Subject:Date:MIME-Version: |
275 | + Content-Type; i=github@github.com; |
276 | + bh=c7fP0xI1KdPdyzII89SvuYNAYaMYAxyGuTNxEPFBYOU=; |
277 | + b=wLrCCki4f2qSp5+WGt8FyNuminCbDu3HoGeh+oNCbyk+hGkX8hVAa+F5s0+81k3f+NedPc8sAKci |
278 | + 3C5mHVcPB5x5iaDjtqTMyHvkZZ2bEzTJsndr66IjRBmncNSgHft7djWdwPRoUWePzZzklzqPAN/2 |
279 | + 7zvPBJdZ5FPkvbW0xxU= |
280 | + Received: from [10.34.27.159] (10.34.140.5) by mail01.resources.github.com id hcatd42tlo8p for <mauro@stalw.art>; Wed, 2 Nov 2022 14:45:38 -0400 (envelope-from <bounce@resources.github.com>) |
281 | + Message-ID: <d4522d31ac474a07bb3cbc49dc2985a1@88570519> |
282 | + X-Binding: 88570519 |
283 | + X-elqSiteID: 88570519 |
284 | + X-elqPod: 0xF1B94DB93BE5675EF0A64610BCE5945D8F0CC1F9D072032A60E02D51CEF3F9E5 |
285 | + X-cid: 2806-2866 |
286 | + List-Unsubscribe: =?utf-8?q?=3Cmailto=3Aspamproc=40fbl=2Een25=2Ecom=3Fsubject=3DListUnsub=5F88570519=5Fd452?= |
287 | + =?utf-8?q?2d31ac474a07bb3cbc49dc2985a1=3E=2C?= |
288 | + =?utf-8?q?_=3Chttp=3A=2F=2Fapp=2Egithub=2Emedia=2Fe=2Fu=3Fs=3D88570519&elq=3Dd4522d31ac474a07b?= |
289 | + =?utf-8?q?b3cbc49dc2985a1&t=3D17=3E?= |
290 | + From: GitHub <github@github.com> |
291 | + Reply-To: GitHub <github@github.com> |
292 | + To: mauro@stalw.art |
293 | + Subject: Copilot: One More Try =?utf-8?b?8J+agA==?= |
294 | + Date: Wed, 02 Nov 2022 14:45:38 -0400 |
295 | + MIME-Version: 1.0 |
296 | + Content-Type: multipart/alternative; boundary="=-Z1XVp+ho2orUDYPPOxt0Ag==" |
297 | + |
298 | + --=-Z1XVp+ho2orUDYPPOxt0Ag== |
299 | + Content-Type: text/plain; charset=utf-8 |
300 | + Content-Transfer-Encoding: base64 |
301 | + Content-Id: <292482a5-7095-483c-81d7-9314f808ecbe> |
302 | + |
303 | + R2V0IG1vcmUgcHJvZHVjdGl2ZSB3aXRoIEdpdEh1YiBDb3BpbG90IHRvZGF5LiAgIA0KDQrN |
304 | + j+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDN |
305 | + j+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDN |
306 | + j+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDN |
307 | + j+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDN |
308 | + j+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDN |
309 | + j+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCAgICDNj+KAjCDNj+KAjCDNj+KAjCDNj+KA |
310 | + jCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KA |
311 | + jCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KA |
312 | + jCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KA |
313 | + jCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KA |
314 | + jCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KA |
315 | + jCDNj+KAjCAgICDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDN |
316 | + j+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDN |
317 | + j+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDN |
318 | + j+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDN |
319 | + j+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDN |
320 | + j+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCAgICDNj+KAjCDNj+KA |
321 | + jCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KA |
322 | + jCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KA |
323 | + jCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KA |
324 | + jCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KA |
325 | + jCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KAjCDNj+KA |
326 | + jCDNj+KAjCDNj+KAjCDNj+KAjCAgIA0KDQoiR2l0SHViIiAgICAgICAgICAgICAgICAgICJH |
327 | + aXRIdWIiICAgICAgICAgICAgICAgIDxodHRwczovL2FwcC5naXRodWIubWVkaWEvZS9lcj9z |
328 | + PTg4NTcwNTE5JmxpZD0zNzk1JmVscVRyYWNrSWQ9ZTg1YThmNTM3ODJjNGMyZTk2NzdiMDNm |
329 | + ZDQ5YTFjMmYmZWxxPWQ0NTIyZDMxYWM0NzRhMDdiYjNjYmM0OWRjMjk4NWExJmVscWFpZD0y |
330 | + ODY2JmVscWF0PTE+ICAgICAgICAgICAgICAgICAgICAgICAgICAiIiAgICAgICAgIDxodHRw |
331 | + czovL2FwcC5naXRodWIubWVkaWEvZS9lcj9zPTg4NTcwNTE5JmxpZD0zNzkyJmVscVRyYWNr |
332 | + SWQ9ZWJjNzZjMWFjOTEzNDViZmExNmIxYTVlNmNhZTBjMmEmZWxxPWQ0NTIyZDMxYWM0NzRh |
333 | + MDdiYjNjYmM0OWRjMjk4NWExJmVscWFpZD0yODY2JmVscWF0PTE+ICAgICAgIA0KU2luY2Ug |
334 | + d2UgbGFzdCBzYXcgeW91LCBvdmVyIDEgTWlsbGlvbiBkZXZlbG9wZXJzIGhhdmUgdHJpZWQg |
335 | + Q29waWxvdCAgICAgICANCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t |
336 | + LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t |
337 | + LS0tLS0NCkFuZCBvdXIgdXNlciByZXNlYXJjaCA8aHR0cHM6Ly9hcHAuZ2l0aHViLm1lZGlh |
338 | + L2UvZXI/cz04ODU3MDUxOSZsaWQ9Mzc5NCZlbHFUcmFja0lkPWQ3NjVlYmE4NTI5ODQzYWNi |
339 | + MDVlODM4YTVhYzk0NjA1JmVscT1kNDUyMmQzMWFjNDc0YTA3YmIzY2JjNDlkYzI5ODVhMSZl |
340 | + bHFhaWQ9Mjg2NiZlbHFhdD0xPiBoYXMgc2hvd24gdGhhdCA4OCUgb2YgZGV2ZWxvcGVycyBm |
341 | + ZWx0IG1vcmUgcHJvZHVjdGl2ZSwgd2l0aCBDb3BpbG90IGhlbHBpbmcgdGhlbSBzdGF5IGlu |
342 | + IHRoZSBmbG93IGFuZCBjb21wbGV0ZSB0YXNrcyBmYXN0ZXIuICAgICAgIA0KSGVyZSdzIHdo |
343 | + YXQgZGV2ZWxvcGVycyBsaWtlIHlvdSBhcmUgc2F5aW5nOiAgICAgICANCuKAnFdlJ3ZlIGJl |
344 | + ZW4gdXNpbmcgR2l0SHViICNDb3BpbG90IGZvciAzIG1vbnRocyBub3cuIEl0IGlzIG9uZSBv |
345 | + ZiB0aG9zZSBwcm9kdWN0cyB0aGF0IHlvdSBkb24ndCB0aGluayB5b3UgbmVlZCAoZXNwZWNp |
346 | + YWxseSBpZiB5b3UgYXJlIGFuIGV4cGVyaWVuY2VkIGVuZ2luZWVyKSwgYnV0IG9uY2UgeW91 |
347 | + J3ZlIHN0YXJ0ZWQgdXNpbmcgaXQsIGl0IGlzIGhhcmQgdG8gaW1hZ2luZSBjb2Rpbmcgd2l0 |
348 | + aG91dCBpdC4gJDEwIHBlciBtb250aCBwcmljaW5nIGlzIGFsc28gZW50aWNpbmcu4oCdICAg |
349 | + ICAgIA0KLSBLaW50YW4gQnJhaG1iaGF0dCAgICAgICANCuKAnFRoaXMgaXMgdGhlIHNpbmds |
350 | + ZSBtb3N0IG1pbmQtYmxvd2luZyBhcHBsaWNhdGlvbiBvZiBNTCBJ4oCZdmUgZXZlciBzZWVu |
351 | + LuKAnSAgICAgICANCi0gTWlrZSBLcmllZ2VyLCBjby1mb3VuZGVyLCBJbnN0YWdyYW0gICAg |
352 | + ICAgDQrigJxUcnlpbmcgdG8gY29kZSBpbiBhbiB1bmZhbWlsaWFyIGxhbmd1YWdlIGJ5IGdv |
353 | + b2dsaW5nIGV2ZXJ5dGhpbmcgaXMgbGlrZSBuYXZpZ2F0aW5nIGEgZm9yZWlnbiBjb3VudHJ5 |
354 | + IHdpdGgganVzdCBhIHBocmFzZSBib29rLiBVc2luZyBHaXRIdWIgQ29waWxvdCBpcyBsaWtl |
355 | + IGhpcmluZyBhbiBpbnRlcnByZXRlciA8aHR0cHM6Ly9hcHAuZ2l0aHViLm1lZGlhL2UvZXI/ |
356 | + cz04ODU3MDUxOSZsaWQ9Mzc5MyZlbHFUcmFja0lkPTRhOGM0NTI2NTY1OTQ3MmI4N2MxNTg3 |
357 | + ZGIxZGIwN2U2JmVscT1kNDUyMmQzMWFjNDc0YTA3YmIzY2JjNDlkYzI5ODVhMSZlbHFhaWQ9 |
358 | + Mjg2NiZlbHFhdD0xPi7igJ0gICAgICAgDQotIEhhcnJpIEVkd2FyZHMsIHNjaWVudGlzdCwg |
359 | + T3BlbkFJICAgICAgIA0KV2FudCB0byBnZXQgbW9yZSBwcm9kdWN0aXZlIHdpdGggR2l0SHVi |
360 | + IENvcGlsb3Q/ICAgICAgIA0KU2lnbiB1cCB0b2RheSAgICAgICAgIDxodHRwczovL2FwcC5n |
361 | + aXRodWIubWVkaWEvZS9lcj9zPTg4NTcwNTE5JmxpZD0zNzkyJmVscVRyYWNrSWQ9MThlMzE2 |
362 | + MmMyYmM5NGY2YWE0ODlkODVmNDkyMDJlZWEmZWxxPWQ0NTIyZDMxYWM0NzRhMDdiYjNjYmM0 |
363 | + OWRjMjk4NWExJmVscWFpZD0yODY2JmVscWF0PTE+ICAgICAgIA0KDQoNCg0KDQpVbnN1YnNj |
364 | + cmliZSA8aHR0cHM6Ly9hcHAuZ2l0aHViLm1lZGlhL2UvdT9zPTg4NTcwNTE5JmVscT1kNDUy |
365 | + MmQzMWFjNDc0YTA3YmIzY2JjNDlkYzI5ODVhMT4gwrcgRW1haWwgcHJlZmVyZW5jZXMgPGh0 |
366 | + dHBzOi8vYXBwLmdpdGh1Yi5tZWRpYS9lL2VyP3M9ODg1NzA1MTkmbGlkPTMwMzUmZWxxVHJh |
367 | + Y2tJZD05MWFhMGQ2Nzg5NGE0NWFmODJiZGNlZTFlZWQyZDBiNyZlbHE9ZDQ1MjJkMzFhYzQ3 |
368 | + NGEwN2JiM2NiYzQ5ZGMyOTg1YTEmZWxxYWlkPTI4NjYmZWxxYXQ9MT4gwrcgVGVybXMgPGh0 |
369 | + dHBzOi8vYXBwLmdpdGh1Yi5tZWRpYS9lL2VyP3M9ODg1NzA1MTkmbGlkPTMwMzQmZWxxVHJh |
370 | + Y2tJZD1jNDdjM2E2OWJlN2U0OTIxYjYwNzUwMDFhM2E0N2U5ZCZlbHE9ZDQ1MjJkMzFhYzQ3 |
371 | + NGEwN2JiM2NiYzQ5ZGMyOTg1YTEmZWxxYWlkPTI4NjYmZWxxYXQ9MT4gwrcgUHJpdmFjeSA8 |
372 | + aHR0cHM6Ly9hcHAuZ2l0aHViLm1lZGlhL2UvZXI/cz04ODU3MDUxOSZsaWQ9MzAzNiZlbHFU |
373 | + cmFja0lkPTY0NTU0NmExNTlkMTRmNmRiZmUxM2U4NjAzOThlODFkJmVscT1kNDUyMmQzMWFj |
374 | + NDc0YTA3YmIzY2JjNDlkYzI5ODVhMSZlbHFhaWQ9Mjg2NiZlbHFhdD0xPiDCtyBTaWduIGlu |
375 | + IHRvIEdpdEh1YiA8aHR0cHM6Ly9hcHAuZ2l0aHViLm1lZGlhL2UvZXI/cz04ODU3MDUxOSZs |
376 | + aWQ9MzAzMyZlbHFUcmFja0lkPWE0NjQ3OWZjYTBmYTQxYmRhNjA2ZjAyYmNjODBiYmVjJmVs |
377 | + cT1kNDUyMmQzMWFjNDc0YTA3YmIzY2JjNDlkYzI5ODVhMSZlbHFhaWQ9Mjg2NiZlbHFhdD0x |
378 | + PiAgICAgDQpHaXRIdWIsIEluYy4NCjg4IENvbGluIFAgS2VsbHkgSnIgU3QuDQpTYW4gRnJh |
379 | + bmNpc2NvLCBDQSA5NDEwNyAgICAgDQoNCg== |
380 | + |
381 | + --=-Z1XVp+ho2orUDYPPOxt0Ag== |
382 | + Content-Type: text/html; charset=utf-8 |
383 | + Content-Transfer-Encoding: base64 |
384 | + Content-Id: <3e08a47e-e741-4db6-bd39-f762c12f6e97> |
385 | + |
386 | + PCFET0NUWVBFIGh0bWw+DQo8aHRtbCBsYW5nPSJlbiIgZGlyPSJsdHIiIHhtbG5zOnY9InVy |
387 | + bjpzY2hlbWFzLW1pY3Jvc29mdC1jb206dm1sIiB4bWxuczpvPSJ1cm46c2NoZW1hcy1taWNy |
388 | + b3NvZnQtY29tOm9mZmljZTpvZmZpY2UiPg0KPGhlYWQ+DQogICAgPG1ldGEgY2hhcnNldD0i |
389 | + dXRmLTgiPg0KICAgIDxtZXRhIGh0dHAtZXF1aXY9IlgtVUEtQ29tcGF0aWJsZSIgY29udGVu |
390 | + dD0iSUU9ZWRnZSI+DQogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRo |
391 | + PWRldmljZS13aWR0aCxpbml0aWFsLXNjYWxlPTEiPg0KICAgIDxtZXRhIG5hbWU9ImZvcm1h |
392 | + dC1kZXRlY3Rpb24iIGNvbnRlbnQ9InRlbGVwaG9uZT1ubywgZGF0ZT1ubywgYWRkcmVzcz1u |
393 | + bywgZW1haWw9bm8iPg0KICAgIDxtZXRhIG5hbWU9IngtYXBwbGUtZGlzYWJsZS1tZXNzYWdl |
394 | + LXJlZm9ybWF0dGluZyI+DQogICAgPG1ldGEgbmFtZT0iY29sb3Itc2NoZW1lIiBjb250ZW50 |
395 | + PSJsaWdodCBkYXJrIj4NCiAgICA8bWV0YSBuYW1lPSJzdXBwb3J0ZWQtY29sb3Itc2NoZW1l |
396 | + cyIgY29udGVudD0ibGlnaHQgZGFyayI+DQogICAgPHRpdGxlPjwvdGl0bGU+DQogICAgPCEt |
397 | + LVtpZiBtc29dPg0KICAgIDx4bWw+DQogICAgPG86T2ZmaWNlRG9jdW1lbnRTZXR0aW5ncz4N |
398 | + CiAgICA8bzpBbGxvd1BORy8+DQogICAgPG86UGl4ZWxzUGVySW5jaD45NjwvbzpQaXhlbHNQ |
399 | + ZXJJbmNoPg0KICAgIDwvbzpPZmZpY2VEb2N1bWVudFNldHRpbmdzPg0KICAgIDwveG1sPg0K |
400 | + ICAgIDwhW2VuZGlmXS0tPg0KICAgIDxzdHlsZT4NCiAgICAgICAgLyogR2VuZXJhbCByZXNl |
401 | + dCBzdHlsZXMuICovDQogICAgICAgIGJvZHksIHRhYmxlLCB0ZCwgYSB7IC13ZWJraXQtdGV4 |
402 | + dC1zaXplLWFkanVzdDogMTAwJTsgLW1zLXRleHQtc2l6ZS1hZGp1c3Q6IDEwMCU7IH0NCiAg |
403 | + ICAgICAgdGFibGUsIHRkIHsgbXNvLXRhYmxlLWxzcGFjZTogMHB0OyBtc28tdGFibGUtcnNw |
404 | + YWNlOiAwcHQ7IH0NCiAgICAgICAgaW1nIHsgLW1zLWludGVycG9sYXRpb24tbW9kZTogYmlj |
405 | + dWJpYzsgfQ0KICAgICAgICBpbWcgeyBib3JkZXI6IDA7IGhlaWdodDogYXV0bzsgbGluZS1o |
406 | + ZWlnaHQ6IDEwMCU7IG91dGxpbmU6IG5vbmU7IHRleHQtZGVjb3JhdGlvbjogbm9uZTsgfQ0K |
407 | + ICAgICAgICB0YWJsZSB7IGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2UgIWltcG9ydGFudDsg |
408 | + fQ0KICAgICAgICBib2R5IHsgaGVpZ2h0OiAxMDAlICFpbXBvcnRhbnQ7IG1hcmdpbjogMCAh |
409 | + aW1wb3J0YW50OyBwYWRkaW5nOiAwICFpbXBvcnRhbnQ7IHdpZHRoOiAxMDAlICFpbXBvcnRh |
410 | + bnQ7IH0NCiAgICAgICAgYVt4LWFwcGxlLWRhdGEtZGV0ZWN0b3JzXSB7IGNvbG9yOiBpbmhl |
411 | + cml0ICFpbXBvcnRhbnQ7IHRleHQtZGVjb3JhdGlvbjogbm9uZSAhaW1wb3J0YW50OyBmb250 |
412 | + LXNpemU6IGluaGVyaXQgIWltcG9ydGFudDsgZm9udC1mYW1pbHk6IGluaGVyaXQgIWltcG9y |
413 | + dGFudDsgZm9udC13ZWlnaHQ6IGluaGVyaXQgIWltcG9ydGFudDsgbGluZS1oZWlnaHQ6IGlu |
414 | + aGVyaXQgIWltcG9ydGFudDsgfQ0KICAgICAgICB1KyNib2R5IGEgeyBjb2xvcjogaW5oZXJp |
415 | + dDsgdGV4dC1kZWNvcmF0aW9uOiBub25lOyBmb250LXNpemU6IGluaGVyaXQ7IGZvbnQtZmFt |
416 | + aWx5OiBpbmhlcml0OyBmb250LXdlaWdodDogaW5oZXJpdDsgbGluZS1oZWlnaHQ6IGluaGVy |
417 | + aXQ7IH0NCiAgICAgICAgI01lc3NhZ2VWaWV3Qm9keSBhIHsgY29sb3I6IGluaGVyaXQ7IHRl |
418 | + eHQtZGVjb3JhdGlvbjogbm9uZTsgZm9udC1zaXplOiBpbmhlcml0OyBmb250LWZhbWlseTog |
419 | + aW5oZXJpdDsgZm9udC13ZWlnaHQ6IGluaGVyaXQ7IGxpbmUtaGVpZ2h0OiBpbmhlcml0OyB9 |
420 | + DQogICAgICAgIHUrLmJvZHkgLmdsaXN0IHsgbWFyZ2luLWxlZnQ6IDAgIWltcG9ydGFudDsg |
421 | + fQ0KDQogICAgICAgIEBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC13aWR0aDogNjQwcHgp |
422 | + IHsNCiAgICAgICAgICAgIHUrLmJvZHkgLmdsaXN0IHsNCiAgICAgICAgICAgICAgICBtYXJn |
423 | + aW4tbGVmdDogNDBweCAhaW1wb3J0YW50Ow0KICAgICAgICAgICAgfQ0KICAgICAgICB9DQoN |
424 | + CiAgICAgICAgLyogU2V0IGRlZmF1bHQgbGluayBzdHlsZXMgaGVyZS4gT3ZlcnJpZGUgdGhl |
425 | + bSBvbiBob3ZlciB0byBzaG93IHVzZXJzIHRoZXkgYXJlIGludGVyYWN0aXZlLiAqLw0KICAg |
426 | + ICAgICBhIHsgY29sb3I6ICMwOTY5ZGE7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IHRleHQtZGVj |
427 | + b3JhdGlvbjogbm9uZTsgfQ0KICAgICAgICBhOmhvdmVyIHsgY29sb3I6ICMwOTY5ZGEgIWlt |
428 | + cG9ydGFudDsgdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmUgIWltcG9ydGFudDsgfQ0KICAg |
429 | + ICAgICBhLmJ1dHRvbiB7IHRleHQtYWxpZ246IGNlbnRlcjsgfQ0KICAgICAgICBhLmJ1dHRv |
430 | + bjpob3ZlciB7IGJhY2tncm91bmQtY29sb3I6ICMyYzk3NGIgIWltcG9ydGFudDsgYm9yZGVy |
431 | + LWNvbG9yOiAjMmM5NzRiICFpbXBvcnRhbnQ7IGNvbG9yOiAjZmZmZmZmICFpbXBvcnRhbnQ7 |
432 | + IHRleHQtZGVjb3JhdGlvbjogbm9uZSAhaW1wb3J0YW50OyB9DQoNCiAgICAgICAgQG1lZGlh |
433 | + IHNjcmVlbiBhbmQgKG1heC13aWR0aDogNTgwcHgpIHsNCiAgICAgICAgICAgIGEuYnV0dG9u |
434 | + IHsgZGlzcGxheTogYmxvY2sgIWltcG9ydGFudDsgfQ0KICAgICAgICB9DQogICAgPC9zdHls |
435 | + ZT4NCiAgICA8IS0tIFNldCB5b3VyIGRhcmsgbW9kZSBzdHlsZXMgYmVsb3cuIC0tPg0KICAg |
436 | + IDxzdHlsZT4NCiAgICAgICAgOnJvb3Qgew0KICAgICAgICAgICAgY29sb3Itc2NoZW1lOiBs |
437 | + aWdodCBkYXJrOw0KICAgICAgICAgICAgc3VwcG9ydGVkLWNvbG9yLXNjaGVtZXM6IGxpZ2h0 |
438 | + IGRhcms7DQogICAgICAgIH0NCg0KICAgICAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2No |
439 | + ZW1lOiBkYXJrICkgew0KICAgICAgICAgICAgYm9keSwgLmVtYWlsLWNvbnRhaW5lciwgLmNv |
440 | + bnRlbnQtY29udGFpbmVyIHsgYmFja2dyb3VuZC1jb2xvcjogIzAwMDAwMCAhaW1wb3J0YW50 |
441 | + OyBjb2xvcjogI2ZmZmZmZiAhaW1wb3J0YW50OyB9DQogICAgICAgICAgICBoMSwgaDIsIGgz |
442 | + LCBzdHJvbmcgeyBjb2xvcjogI2ZmZmZmZiAhaW1wb3J0YW50OyB9DQogICAgICAgICAgICBh |
443 | + IHsgY29sb3I6ICNmZmZmZmYgIWltcG9ydGFudDsgdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxp |
444 | + bmUgIWltcG9ydGFudDsgfQ0KICAgICAgICAgICAgLmZvb3RlciBhIHsgY29sb3I6ICM5OTk5 |
445 | + OTkgIWltcG9ydGFudDsgdGV4dC1kZWNvcmF0aW9uOiBub25lICFpbXBvcnRhbnQ7IH0NCiAg |
446 | + ICAgICAgICAgIGE6aG92ZXIgeyBjb2xvcjogI2ZmZmZmZiAhaW1wb3J0YW50OyB0ZXh0LWRl |
447 | + Y29yYXRpb246IG5vbmUgIWltcG9ydGFudDsgfQ0KICAgICAgICAgICAgLmZvb3RlciBhOmhv |
448 | + dmVyIHsgdGV4dC1kZWNvcmF0aW9uOiB1bmRlcmxpbmUgIWltcG9ydGFudDsgfQ0KICAgICAg |
449 | + ICAgICAgYS5idXR0b24geyBjb2xvcjogI2ZmZmZmZiAhaW1wb3J0YW50OyB0ZXh0LWRlY29y |
450 | + YXRpb246IG5vbmUgIWltcG9ydGFudDsgfQ0KICAgICAgICAgICAgLmRhcmstbW9kZS1oaWRl |
451 | + IHsgZGlzcGxheTogbm9uZSAhaW1wb3J0YW50OyB9DQogICAgICAgICAgICAuZGFyay1tb2Rl |
452 | + LXNob3cgeyBkaXNwbGF5OiBibG9jayAhaW1wb3J0YW50OyB9DQogICAgICAgIH0NCiAgICA8 |
453 | + L3N0eWxlPg0KPC9oZWFkPg0KPGJvZHkgaWQ9ImJvZHkiIGNsYXNzPSJib2R5IiBzdHlsZT0i |
454 | + YmFja2dyb3VuZC1jb2xvcjogI2ZmZmZmZjsgbWFyZ2luOiAwICFpbXBvcnRhbnQ7IHBhZGRp |
455 | + bmc6IDAgIWltcG9ydGFudDsiPg0KDQoNCg0KDQoNCiAgICA8ZGl2IHN0eWxlPSJkaXNwbGF5 |
456 | + OiBub25lOyBtYXgtaGVpZ2h0OiAwOyBvdmVyZmxvdzogaGlkZGVuOyI+DQogICAgICAgIEdl |
457 | + dCBtb3JlIHByb2R1Y3RpdmUgd2l0aCBHaXRIdWIgQ29waWxvdCB0b2RheS4NCiAgICA8L2Rp |
458 | + dj4NCiAgICA8ZGl2IHN0eWxlPSJkaXNwbGF5OiBub25lOyBtYXgtaGVpZ2h0OiAwcHg7IG92 |
459 | + ZXJmbG93OiBoaWRkZW47Ij4NCiAgICANCiAgICANCiYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
460 | + OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
461 | + OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
462 | + OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
463 | + OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
464 | + OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
465 | + OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
466 | + OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
467 | + OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
468 | + OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
469 | + OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
470 | + OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
471 | + OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
472 | + OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
473 | + OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
474 | + OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
475 | + OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3 |
476 | + OyZ6d25qOyZuYnNwOw0KICAgIA0KJiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
477 | + c3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
478 | + c3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
479 | + c3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
480 | + c3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
481 | + c3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
482 | + c3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
483 | + c3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
484 | + c3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
485 | + c3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
486 | + c3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
487 | + c3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
488 | + c3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
489 | + c3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
490 | + c3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
491 | + c3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
492 | + c3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5ic3A7JiM4NDc7Jnp3bmo7Jm5i |
493 | + c3A7DQogICAgDQomIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0Nzsm |
494 | + enduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0Nzsm |
495 | + enduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0Nzsm |
496 | + enduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0Nzsm |
497 | + enduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0Nzsm |
498 | + enduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0Nzsm |
499 | + enduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0Nzsm |
500 | + enduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0Nzsm |
501 | + enduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0Nzsm |
502 | + enduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0Nzsm |
503 | + enduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0Nzsm |
504 | + enduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0Nzsm |
505 | + enduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0Nzsm |
506 | + enduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0Nzsm |
507 | + enduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0Nzsm |
508 | + enduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0Nzsm |
509 | + enduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsmIzg0NzsmenduajsmbmJzcDsNCiAgICAN |
510 | + CiYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNw |
511 | + OyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNw |
512 | + OyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNw |
513 | + OyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNw |
514 | + OyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNw |
515 | + OyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNw |
516 | + OyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNw |
517 | + OyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNw |
518 | + OyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNw |
519 | + OyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNw |
520 | + OyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNw |
521 | + OyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNw |
522 | + OyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNw |
523 | + OyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNw |
524 | + OyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNw |
525 | + OyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNw |
526 | + OyYjODQ3OyZ6d25qOyZuYnNwOyYjODQ3OyZ6d25qOyZuYnNwOw0KICAgIDwvZGl2Pg0KDQog |
527 | + ICAgPCEtLSBUaGlzIGlzIHRoZSBtYWluIGNvbnRhaW5lciBkaXYsIHdoZXJlIHdlIHNldCBz |
528 | + b21lIGFjY2Vzc2liaWxpdHkgYmFzaWNzLiAtLT4NCiAgICA8ZGl2IGNsYXNzPSJlbWFpbC1j |
529 | + b250YWluZXIiIHJvbGU9ImFydGljbGUiIGFyaWEtcm9sZWRlc2NyaXB0aW9uPSJlbWFpbCIg |
530 | + YXJpYS1sYWJlbD0iIiBsYW5nPSJlbiIgZGlyPSJsdHIiIHN0eWxlPSJiYWNrZ3JvdW5kLWNv |
531 | + bG9yOiAjZmZmZmZmOyBmb250LXNpemU6IG1lZGl1bTsgZm9udC1zaXplOiBtYXgoMTZweCwg |
532 | + MXJlbSk7Ij4NCiAgICAgICAgPCEtLVtpZiAoZ3RlIG1zbyA5KXwoSUUpXT4NCiAgICAgICAg |
533 | + PHRhYmxlIGNlbGxzcGFjaW5nPSIwIiBjZWxscGFkZGluZz0iMCIgYm9yZGVyPSIwIiB3aWR0 |
534 | + aD0iNTgwIiBhbGlnbj0iY2VudGVyIiByb2xlPSJwcmVzZW50YXRpb24iPjx0cj48dGQ+DQog |
535 | + ICAgICAgIDwhW2VuZGlmXS0tPg0KICAgICAgICA8ZGl2IGNsYXNzPSJjb250ZW50LWNvbnRh |
536 | + aW5lciIgc3R5bGU9ImNvbG9yOiAjMDAwMDAwOyBmb250LWZhbWlseTogLWFwcGxlLXN5c3Rl |
537 | + bSwgQmxpbmtNYWNTeXN0ZW1Gb250LCAnU2Vnb2UgVUknLCBIZWx2ZXRpY2EsIEFyaWFsLCBz |
538 | + YW5zLXNlcmlmLCAnQXBwbGUgQ29sb3IgRW1vamknLCAnU2Vnb2UgVUkgRW1vamknLCAnU2Vn |
539 | + b2UgVUkgU3ltYm9sJzsgbGluZS1oZWlnaHQ6IDEuNTsgbWFyZ2luOiAxcmVtIGF1dG87IG1h |
540 | + eC13aWR0aDogNTgwcHg7IHBhZGRpbmc6IDFlbTsiPg0KDQogICAgICAgICAgICA8YSBocmVm |
541 | + PSJodHRwczovL2FwcC5naXRodWIubWVkaWEvZS9lcj9zPTg4NTcwNTE5JmxpZD0zNzk1JmVs |
542 | + cVRyYWNrSWQ9ZTg1YThmNTM3ODJjNGMyZTk2NzdiMDNmZDQ5YTFjMmYmZWxxPWQ0NTIyZDMx |
543 | + YWM0NzRhMDdiYjNjYmM0OWRjMjk4NWExJmVscWFpZD0yODY2JmVscWF0PTEiIHN0eWxlPSJk |
544 | + aXNwbGF5OiBibG9jazsgbWFyZ2luOiAwIDAgMmVtIDA7Ij4NCiAgICAgICAgICAgICAgICA8 |
545 | + aW1nIGFsdD0iR2l0SHViIiBzcmM9Imh0dHBzOi8vaW1hZ2VzLmdpdGh1Yi5tZWRpYS9FbG9x |
546 | + dWFJbWFnZXMvY2xpZW50cy9HaXRIdWJJbmMvJTdCMDY5NTg1NTUtYjE1OC00M2Y5LTlmNGMt |
547 | + ZjdjYzEwYTMwNWEwJTdEX2dpdGh1Yi1sb2dvLWVtYWlsLnBuZyIgd2lkdGg9IjEwMCIgYm9y |
548 | + ZGVyPSIwIiBzdHlsZT0iZGlzcGxheTogYmxvY2s7IiBjbGFzcz0iZGFyay1tb2RlLWhpZGUi |
549 | + Pg0KDQogICAgICAgICAgICAgICAgPCEtLVtpZiAhbXNvXT48IS0tPg0KICAgICAgICAgICAg |
550 | + ICAgIDxpbWcgYWx0PSJHaXRIdWIiIHNyYz0iaHR0cHM6Ly9pbWFnZXMuZ2l0aHViLm1lZGlh |
551 | + L0Vsb3F1YUltYWdlcy9jbGllbnRzL0dpdEh1YkluYy8lN0IzNjljZmYzNS05MjQ0LTQyMGUt |
552 | + OWE4My0xYTkwNDBkOWUzMTElN0RfZ2l0aHViLWxvZ28tZW1haWwtZGFyay1tb2RlLnBuZyIg |
553 | + d2lkdGg9IjEwMCIgYm9yZGVyPSIwIiBzdHlsZT0iZGlzcGxheTogbm9uZTsiIGNsYXNzPSJk |
554 | + YXJrLW1vZGUtc2hvdyI+DQogICAgICAgICAgICAgICAgPCEtLTwhW2VuZGlmXS0tPiANCiAg |
555 | + ICAgICAgICAgIDwvYT4NCg0KICAgICAgICAgICAgPHAgc3R5bGU9Im1hcmdpbjogMmVtIDAg |
556 | + MmVtIDA7Ij4NCiAgICAgICAgICAgICAgICA8YSBocmVmPSJodHRwczovL2FwcC5naXRodWIu |
557 | + bWVkaWEvZS9lcj9zPTg4NTcwNTE5JmxpZD0zNzkyJmVscVRyYWNrSWQ9ZWJjNzZjMWFjOTEz |
558 | + NDViZmExNmIxYTVlNmNhZTBjMmEmZWxxPWQ0NTIyZDMxYWM0NzRhMDdiYjNjYmM0OWRjMjk4 |
559 | + NWExJmVscWFpZD0yODY2JmVscWF0PTEiPg0KICAgICAgICAgICAgICAgICAgICA8aW1nIGFs |
560 | + dD0iIiBzcmM9Imh0dHBzOi8vaW1hZ2VzLmdpdGh1Yi5tZWRpYS9FbG9xdWFJbWFnZXMvY2xp |
561 | + ZW50cy9HaXRIdWJJbmMvJTdCMzMzZGI2NTctNmYwOC00ZTYzLTg3OWQtOTljYmM0MWM2Mjg1 |
562 | + JTdEX0NvcGlsb3QuanBnIiB3aWR0aD0iNTgwIiBib3JkZXI9IjAiIHN0eWxlPSJib3JkZXIt |
563 | + cmFkaXVzOiA0cHg7IGNvbG9yOiAjMDAwMDAwOyBmb250LXNpemU6IDEuOGVtOyBsaW5lLWhl |
564 | + aWdodDogMS40OyBkaXNwbGF5OiBibG9jazsgbWF4LXdpZHRoOiAxMDAlOyBtaW4td2lkdGg6 |
565 | + IDEwMHB4OyB3aWR0aDogMTAwJTsiPg0KICAgICAgICAgICAgICAgIDwvYT4NCiAgICAgICAg |
566 | + ICAgIDwvcD4NCg0KICAgICAgICAgICAgPGgxIHN0eWxlPSJjb2xvcjogIzIyMjIyMjsgZm9u |
567 | + dC1zaXplOiAxLjhlbTsgZm9udC13ZWlnaHQ6IGJvbGQ7IGxpbmUtaGVpZ2h0OiAxLjI7IG1h |
568 | + cmdpbjogMCAwIDAgMDsgdGV4dC1hbGlnbjogY2VudGVyOyI+DQogICAgICAgICAgICAgICAg |
569 | + U2luY2Ugd2UgbGFzdCBzYXcgeW91LCBvdmVyIDEgTWlsbGlvbiBkZXZlbG9wZXJzIGhhdmUg |
570 | + dHJpZWQgQ29waWxvdA0KICAgICAgICAgICAgPC9oMT4NCg0KICAgICAgICAgICAgPHAgc3R5 |
571 | + bGU9Im1hcmdpbjogMmVtIDAgMmVtIDA7Ij4NCiAgICAgICAgICAgICAgICBBbmQgPGEgaHJl |
572 | + Zj0iaHR0cHM6Ly9hcHAuZ2l0aHViLm1lZGlhL2UvZXI/cz04ODU3MDUxOSZsaWQ9Mzc5NCZl |
573 | + bHFUcmFja0lkPWQ3NjVlYmE4NTI5ODQzYWNiMDVlODM4YTVhYzk0NjA1JmVscT1kNDUyMmQz |
574 | + MWFjNDc0YTA3YmIzY2JjNDlkYzI5ODVhMSZlbHFhaWQ9Mjg2NiZlbHFhdD0xIiBzdHlsZT0i |
575 | + Y29sb3I6ICMwOTY5ZGE7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IHRleHQtZGVjb3JhdGlvbjog |
576 | + bm9uZTsiPm91ciB1c2VyIHJlc2VhcmNoPC9hPiBoYXMgc2hvd24gdGhhdCA4OCUgb2YgZGV2 |
577 | + ZWxvcGVycyBmZWx0IG1vcmUgcHJvZHVjdGl2ZSwgd2l0aCBDb3BpbG90IGhlbHBpbmcgdGhl |
578 | + bSBzdGF5IGluIHRoZSBmbG93IGFuZCBjb21wbGV0ZSB0YXNrcyBmYXN0ZXIuDQogICAgICAg |
579 | + ICAgICA8L3A+DQoNCiAgICAgICAgICAgIDxwIHN0eWxlPSJtYXJnaW46IDJlbSAwIDJlbSAw |
580 | + OyI+DQogICAgICAgICAgICAgICAgSGVyZSdzIHdoYXQgZGV2ZWxvcGVycyBsaWtlIHlvdSBh |
581 | + cmUgc2F5aW5nOg0KICAgICAgICAgICAgPC9wPg0KDQogICAgICAgICAgICA8cCBzdHlsZT0i |
582 | + bWFyZ2luOiAyZW0gMCAwIDA7Ij4NCiAgICAgICAgICAgICAgICDigJxXZSd2ZSBiZWVuIHVz |
583 | + aW5nIEdpdEh1YiAjQ29waWxvdCBmb3IgMyBtb250aHMgbm93LiBJdCBpcyBvbmUgb2YgdGhv |
584 | + c2UgcHJvZHVjdHMgdGhhdCB5b3UgZG9uJ3QgdGhpbmsgeW91IG5lZWQgKGVzcGVjaWFsbHkg |
585 | + aWYgeW91IGFyZSBhbiBleHBlcmllbmNlZCBlbmdpbmVlciksIGJ1dCBvbmNlIHlvdSd2ZSBz |
586 | + dGFydGVkIHVzaW5nIGl0LCBpdCBpcyBoYXJkIHRvIGltYWdpbmUgY29kaW5nIHdpdGhvdXQg |
587 | + aXQuICQxMCBwZXIgbW9udGggcHJpY2luZyBpcyBhbHNvIGVudGljaW5nLuKAnQ0KICAgICAg |
588 | + ICAgICAgPC9wPg0KDQogICAgICAgICAgICA8cCBzdHlsZT0ibWFyZ2luOiAxZW0gMCAyZW0g |
589 | + MDsiPg0KICAgICAgICAgICAgICAgIDxzdHJvbmc+LSBLaW50YW4gQnJhaG1iaGF0dDwvc3Ry |
590 | + b25nPg0KICAgICAgICAgICAgPC9wPg0KDQogICAgICAgICAgICA8cCBzdHlsZT0ibWFyZ2lu |
591 | + OiAyZW0gMCAwIDA7Ij4NCiAgICAgICAgICAgICAgICDigJxUaGlzIGlzIHRoZSBzaW5nbGUg |
592 | + bW9zdCBtaW5kLWJsb3dpbmcgYXBwbGljYXRpb24gb2YgTUwgSeKAmXZlIGV2ZXIgc2Vlbi7i |
593 | + gJ0NCiAgICAgICAgICAgIDwvcD4NCg0KICAgICAgICAgICAgPHAgc3R5bGU9Im1hcmdpbjog |
594 | + MWVtIDAgMmVtIDA7Ij4NCiAgICAgICAgICAgICAgICA8c3Ryb25nPi0gTWlrZSBLcmllZ2Vy |
595 | + LCBjby1mb3VuZGVyLCBJbnN0YWdyYW08L3N0cm9uZz4NCiAgICAgICAgICAgIDwvcD4NCg0K |
596 | + ICAgICAgICAgICAgPHAgc3R5bGU9Im1hcmdpbjogMmVtIDAgMCAwOyI+DQogICAgICAgICAg |
597 | + ICAgICAg4oCcVHJ5aW5nIHRvIGNvZGUgaW4gYW4gdW5mYW1pbGlhciBsYW5ndWFnZSBieSBn |
598 | + b29nbGluZyBldmVyeXRoaW5nIGlzIGxpa2UgbmF2aWdhdGluZyBhIGZvcmVpZ24gY291bnRy |
599 | + eSB3aXRoIGp1c3QgYSBwaHJhc2UgYm9vay4gPGEgaHJlZj0iaHR0cHM6Ly9hcHAuZ2l0aHVi |
600 | + Lm1lZGlhL2UvZXI/cz04ODU3MDUxOSZsaWQ9Mzc5MyZlbHFUcmFja0lkPTRhOGM0NTI2NTY1 |
601 | + OTQ3MmI4N2MxNTg3ZGIxZGIwN2U2JmVscT1kNDUyMmQzMWFjNDc0YTA3YmIzY2JjNDlkYzI5 |
602 | + ODVhMSZlbHFhaWQ9Mjg2NiZlbHFhdD0xIiBzdHlsZT0iY29sb3I6ICMwOTY5ZGE7IGZvbnQt |
603 | + d2VpZ2h0OiBub3JtYWw7IHRleHQtZGVjb3JhdGlvbjogbm9uZTsiPlVzaW5nIEdpdEh1YiBD |
604 | + b3BpbG90IGlzIGxpa2UgaGlyaW5nIGFuIGludGVycHJldGVyPC9hPi7igJ0NCiAgICAgICAg |
605 | + ICAgIDwvcD4NCg0KICAgICAgICAgICAgPHAgc3R5bGU9Im1hcmdpbjogMWVtIDAgMmVtIDA7 |
606 | + Ij4NCiAgICAgICAgICAgICAgICA8c3Ryb25nPi0gSGFycmkgRWR3YXJkcywgc2NpZW50aXN0 |
607 | + LCBPcGVuQUk8L3N0cm9uZz4NCiAgICAgICAgICAgIDwvcD4NCg0KICAgICAgICAgICAgPHAg |
608 | + c3R5bGU9Im1hcmdpbjogMmVtIDAgMmVtIDA7Ij4NCiAgICAgICAgICAgICAgICBXYW50IHRv |
609 | + IGdldCBtb3JlIHByb2R1Y3RpdmUgd2l0aCBHaXRIdWIgQ29waWxvdD8NCiAgICAgICAgICAg |
610 | + IDwvcD4NCg0KICAgICAgICAgICAgPCEtLSBBIGJ1dHRvbiBibG9jay4gLS0+DQogICAgICAg |
611 | + ICAgICA8cCBzdHlsZT0ibWFyZ2luOiAyZW0gMCA2ZW0gMDsgdGV4dC1hbGlnbjogY2VudGVy |
612 | + OyI+DQogICAgICAgICAgICAgICAgPGEgY2xhc3M9ImJ1dHRvbiIgaHJlZj0iaHR0cHM6Ly9h |
613 | + cHAuZ2l0aHViLm1lZGlhL2UvZXI/cz04ODU3MDUxOSZsaWQ9Mzc5MiZlbHFUcmFja0lkPTE4 |
614 | + ZTMxNjJjMmJjOTRmNmFhNDg5ZDg1ZjQ5MjAyZWVhJmVscT1kNDUyMmQzMWFjNDc0YTA3YmIz |
615 | + Y2JjNDlkYzI5ODVhMSZlbHFhaWQ9Mjg2NiZlbHFhdD0xIiBzdHlsZT0iYmFja2dyb3VuZDog |
616 | + IzJlYTQ0ZjsgYm9yZGVyOiAycHggc29saWQgIzJlYTQ0ZjsgdGV4dC1kZWNvcmF0aW9uOiBu |
617 | + b25lOyBwYWRkaW5nOiAxMHB4IDIwcHg7IGNvbG9yOiAjZmZmZmZmOyBib3JkZXItcmFkaXVz |
618 | + OiA0cHg7IGRpc3BsYXk6IGlubGluZS1ibG9jazsgZm9udC13ZWlnaHQ6IGJvbGQ7IG1zby1w |
619 | + YWRkaW5nLWFsdDogMDsgdGV4dC11bmRlcmxpbmUtY29sb3I6ICMyZWE0NGYiPjwhLS1baWYg |
620 | + bXNvXT48aSBzdHlsZT0ibGV0dGVyLXNwYWNpbmc6IDIwcHg7bXNvLWZvbnQtd2lkdGg6LTEw |
621 | + MCU7bXNvLXRleHQtcmFpc2U6MzBwdCIgaGlkZGVuPiZuYnNwOzwvaT48IVtlbmRpZl0tLT48 |
622 | + c3BhbiBzdHlsZT0ibXNvLXRleHQtcmFpc2U6MTVwdDsiPlNpZ24gdXAgdG9kYXk8L3NwYW4+ |
623 | + PCEtLVtpZiBtc29dPjxpIHN0eWxlPSJsZXR0ZXItc3BhY2luZzogMjVweDttc28tZm9udC13 |
624 | + aWR0aDotMTAwJSIgaGlkZGVuPiZuYnNwOzwvaT48IVtlbmRpZl0tLT4NCiAgICAgICAgICAg |
625 | + ICAgICA8L2E+DQogICAgICAgICAgICA8L3A+DQoNCiAgICAgICAgPC9kaXY+DQogICAgICAg |
626 | + IDwhLS1baWYgKGd0ZSBtc28gOSl8KElFKV0+DQogICAgICAgIDwvdGQ+PC90cj48L3RhYmxl |
627 | + Pg0KICAgICAgICA8IVtlbmRpZl0tLT4NCiAgICA8L2Rpdj4NCg0KDQoNCjwhLS1baWYgKGd0 |
628 | + ZSBtc28gOSl8KElFKV0+DQo8dGFibGUgY2VsbHNwYWNpbmc9IjAiIGNlbGxwYWRkaW5nPSIw |
629 | + IiBib3JkZXI9IjAiIHdpZHRoPSI1ODAiIGFsaWduPSJjZW50ZXIiIHJvbGU9InByZXNlbnRh |
630 | + dGlvbiI+PHRyPjx0ZD4NCjwhW2VuZGlmXS0tPg0KPGRpdiBjbGFzcz0iY29udGVudC1jb250 |
631 | + YWluZXIiIHN0eWxlPSJjb2xvcjogIzAwMDAwMDsgZm9udC1mYW1pbHk6IC1hcHBsZS1zeXN0 |
632 | + ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgJ1NlZ29lIFVJJywgSGVsdmV0aWNhLCBBcmlhbCwg |
633 | + c2Fucy1zZXJpZiwgJ0FwcGxlIENvbG9yIEVtb2ppJywgJ1NlZ29lIFVJIEVtb2ppJywgJ1Nl |
634 | + Z29lIFVJIFN5bWJvbCc7IGxpbmUtaGVpZ2h0OiAxLjU7IG1hcmdpbjogMXJlbSBhdXRvOyBt |
635 | + YXgtd2lkdGg6IDU4MHB4OyBwYWRkaW5nOiAxZW07Ij4NCiAgICA8ZGl2IGNsYXNzPSJmb290 |
636 | + ZXIiPg0KICAgICAgICA8aHIgc3R5bGU9ImJvcmRlci13aWR0aDogMDsgYmFja2dyb3VuZDog |
637 | + I2VhZWFlYTsgY29sb3I6ICNlYWVhZWE7IGhlaWdodDogMXB4Ij4NCiAgICAgICAgPHAgc3R5 |
638 | + bGU9ImNvbG9yOiAjOTk5OTk5OyBmb250LXNpemU6IDAuODc1cmVtOyBtYXJnaW46IDJlbSAw |
639 | + IDFlbSAwOyI+DQogICAgICAgICAgICA8YSBkYXRhLXRhcmdldHR5cGU9InN5c2FjdGlvbiIg |
640 | + aHJlZj0iaHR0cHM6Ly9hcHAuZ2l0aHViLm1lZGlhL2UvdT9zPTg4NTcwNTE5JmVscT1kNDUy |
641 | + MmQzMWFjNDc0YTA3YmIzY2JjNDlkYzI5ODVhMSIgY2xhc3M9ImZvb3Rlci1saW5rIiBzdHls |
642 | + ZT0iY29sb3I6ICM5OTk5OTk7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IHRleHQtZGVjb3JhdGlv |
643 | + bjogbm9uZTsiPlVuc3Vic2NyaWJlPC9hPiAmbWlkZG90OyA8YSBocmVmPSJodHRwczovL2Fw |
644 | + cC5naXRodWIubWVkaWEvZS9lcj9zPTg4NTcwNTE5JmxpZD0zMDM1JmVscVRyYWNrSWQ9OTFh |
645 | + YTBkNjc4OTRhNDVhZjgyYmRjZWUxZWVkMmQwYjcmZWxxPWQ0NTIyZDMxYWM0NzRhMDdiYjNj |
646 | + YmM0OWRjMjk4NWExJmVscWFpZD0yODY2JmVscWF0PTEiIGNsYXNzPSJmb290ZXItbGluayIg |
647 | + c3R5bGU9ImNvbG9yOiAjOTk5OTk5OyBmb250LXdlaWdodDogbm9ybWFsOyB0ZXh0LWRlY29y |
648 | + YXRpb246IG5vbmU7Ij5FbWFpbCBwcmVmZXJlbmNlczwvYT4gJm1pZGRvdDsgPGEgaHJlZj0i |
649 | + aHR0cHM6Ly9hcHAuZ2l0aHViLm1lZGlhL2UvZXI/cz04ODU3MDUxOSZsaWQ9MzAzNCZlbHFU |
650 | + cmFja0lkPWM0N2MzYTY5YmU3ZTQ5MjFiNjA3NTAwMWEzYTQ3ZTlkJmVscT1kNDUyMmQzMWFj |
651 | + NDc0YTA3YmIzY2JjNDlkYzI5ODVhMSZlbHFhaWQ9Mjg2NiZlbHFhdD0xIiBjbGFzcz0iZm9v |
652 | + dGVyLWxpbmsiIHN0eWxlPSJjb2xvcjogIzk5OTk5OTsgZm9udC13ZWlnaHQ6IG5vcm1hbDsg |
653 | + dGV4dC1kZWNvcmF0aW9uOiBub25lOyI+VGVybXM8L2E+ICZtaWRkb3Q7IDxhIGhyZWY9Imh0 |
654 | + dHBzOi8vYXBwLmdpdGh1Yi5tZWRpYS9lL2VyP3M9ODg1NzA1MTkmbGlkPTMwMzYmZWxxVHJh |
655 | + Y2tJZD02NDU1NDZhMTU5ZDE0ZjZkYmZlMTNlODYwMzk4ZTgxZCZlbHE9ZDQ1MjJkMzFhYzQ3 |
656 | + NGEwN2JiM2NiYzQ5ZGMyOTg1YTEmZWxxYWlkPTI4NjYmZWxxYXQ9MSIgY2xhc3M9ImZvb3Rl |
657 | + ci1saW5rIiBzdHlsZT0iY29sb3I6ICM5OTk5OTk7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IHRl |
658 | + eHQtZGVjb3JhdGlvbjogbm9uZTsiPlByaXZhY3k8L2E+ICZtaWRkb3Q7IDxhIGhyZWY9Imh0 |
659 | + dHBzOi8vYXBwLmdpdGh1Yi5tZWRpYS9lL2VyP3M9ODg1NzA1MTkmbGlkPTMwMzMmZWxxVHJh |
660 | + Y2tJZD1hNDY0NzlmY2EwZmE0MWJkYTYwNmYwMmJjYzgwYmJlYyZlbHE9ZDQ1MjJkMzFhYzQ3 |
661 | + NGEwN2JiM2NiYzQ5ZGMyOTg1YTEmZWxxYWlkPTI4NjYmZWxxYXQ9MSIgY2xhc3M9ImZvb3Rl |
662 | + ci1saW5rIiBzdHlsZT0iY29sb3I6ICM5OTk5OTk7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IHRl |
663 | + eHQtZGVjb3JhdGlvbjogbm9uZTsiPlNpZ24gaW4gdG8gR2l0SHViPC9hPg0KICAgICAgICA8 |
664 | + L3A+DQogICAgICAgIDxwIHN0eWxlPSJjb2xvcjogIzk5OTk5OTsgZm9udC1zaXplOiAwLjg3 |
665 | + NXJlbTsgbWFyZ2luOiAwIDAgMCAwOyI+DQogICAgICAgICAgICBHaXRIdWIsIEluYy48YnI+ |
666 | + DQogICAgICAgICAgICA4OCBDb2xpbiBQIEtlbGx5IEpyIFN0Ljxicj4NCiAgICAgICAgICAg |
667 | + IFNhbiBGcmFuY2lzY28sIENBIDk0MTA3DQogICAgICAgIDwvcD4NCiAgICA8L2Rpdj4NCjwh |
668 | + LS1baWYgKGd0ZSBtc28gOSl8KElFKV0+DQo8L3RkPjwvdHI+PC90YWJsZT4NCjwhW2VuZGlm |
669 | + XS0tPg0KPC9kaXY+DQogIA0KDQo8aW1nIHNyYz0naHR0cHM6Ly9hcHAuZ2l0aHViLm1lZGlh |
670 | + L2UvRm9vdGVySW1hZ2VzL0Zvb3RlckltYWdlMT9lbHE9ZDQ1MjJkMzFhYzQ3NGEwN2JiM2Ni |
671 | + YzQ5ZGMyOTg1YTEmc2l0ZWlkPTg4NTcwNTE5JyBhbHQ9IiIgYm9yZGVyPTAgd2lkdGg9MXB4 |
672 | + IGhlaWdodD0xcHg+PC9ib2R5PjwvaHRtbD4= |
673 | + |
674 | + --=-Z1XVp+ho2orUDYPPOxt0Ag==-- |
675 | diff --git a/src/arc/mod.rs b/src/arc/mod.rs |
676 | index 7ce5a3d..4cdf6f9 100644 |
677 | --- a/src/arc/mod.rs |
678 | +++ b/src/arc/mod.rs |
679 | @@ -1,38 +1,37 @@ |
680 | pub mod parse; |
681 | - pub mod verify; |
682 | |
683 | use std::borrow::Cow; |
684 | |
685 | use crate::{ |
686 | - common::headers::Header, |
687 | + common::{headers::Header, verify::VerifySignature}, |
688 | dkim::{Algorithm, Canonicalization}, |
689 | }; |
690 | |
691 | #[derive(Debug, PartialEq, Eq, Clone)] |
692 | pub struct Signature<'x> { |
693 | pub(crate) i: u32, |
694 | - a: Algorithm, |
695 | - d: Cow<'x, [u8]>, |
696 | - s: Cow<'x, [u8]>, |
697 | - b: Vec<u8>, |
698 | - bh: Vec<u8>, |
699 | - h: Vec<Vec<u8>>, |
700 | - z: Vec<Vec<u8>>, |
701 | - l: u64, |
702 | - x: u64, |
703 | - t: u64, |
704 | - ch: Canonicalization, |
705 | - cb: Canonicalization, |
706 | + pub(crate) a: Algorithm, |
707 | + pub(crate) d: Cow<'x, [u8]>, |
708 | + pub(crate) s: Cow<'x, [u8]>, |
709 | + pub(crate) b: Vec<u8>, |
710 | + pub(crate) bh: Vec<u8>, |
711 | + pub(crate) h: Vec<Vec<u8>>, |
712 | + pub(crate) z: Vec<Vec<u8>>, |
713 | + pub(crate) l: u64, |
714 | + pub(crate) x: u64, |
715 | + pub(crate) t: u64, |
716 | + pub(crate) ch: Canonicalization, |
717 | + pub(crate) cb: Canonicalization, |
718 | } |
719 | |
720 | #[derive(Debug, PartialEq, Eq, Clone)] |
721 | pub struct Seal<'x> { |
722 | pub(crate) i: u32, |
723 | - a: Algorithm, |
724 | - b: Vec<u8>, |
725 | - d: Cow<'x, [u8]>, |
726 | - s: Cow<'x, [u8]>, |
727 | - t: u64, |
728 | + pub(crate) a: Algorithm, |
729 | + pub(crate) b: Vec<u8>, |
730 | + pub(crate) d: Cow<'x, [u8]>, |
731 | + pub(crate) s: Cow<'x, [u8]>, |
732 | + pub(crate) t: u64, |
733 | pub(crate) cv: ChainValidation, |
734 | } |
735 | |
736 | @@ -55,22 +54,22 @@ pub enum ChainValidation { |
737 | Pass, |
738 | } |
739 | |
740 | - #[derive(Debug)] |
741 | - pub enum Error { |
742 | - ParseError, |
743 | - InvalidInstance, |
744 | - InvalidChainValidation, |
745 | - MissingParameters, |
746 | - Base64, |
747 | - HasHeaderTag, |
748 | - BrokenArcChain, |
749 | - DKIM(crate::dkim::Error), |
750 | - } |
751 | + impl<'x> VerifySignature for Signature<'x> { |
752 | + fn b(&self) -> &[u8] { |
753 | + &self.b |
754 | + } |
755 | |
756 | - impl From<crate::dkim::Error> for Error { |
757 | - fn from(err: crate::dkim::Error) -> Self { |
758 | - Error::DKIM(err) |
759 | + fn a(&self) -> Algorithm { |
760 | + self.a |
761 | } |
762 | } |
763 | |
764 | - pub type Result<T> = std::result::Result<T, Error>; |
765 | + impl<'x> VerifySignature for Seal<'x> { |
766 | + fn b(&self) -> &[u8] { |
767 | + &self.b |
768 | + } |
769 | + |
770 | + fn a(&self) -> Algorithm { |
771 | + self.a |
772 | + } |
773 | + } |
774 | diff --git a/src/arc/parse.rs b/src/arc/parse.rs |
775 | index e995d21..3bd46ee 100644 |
776 | --- a/src/arc/parse.rs |
777 | +++ b/src/arc/parse.rs |
778 | @@ -3,9 +3,10 @@ use mail_parser::decoders::base64::base64_decode_stream; |
779 | use crate::{ |
780 | common::parse::TagParser, |
781 | dkim::{parse::SignatureParser, Algorithm, Canonicalization}, |
782 | + Error, |
783 | }; |
784 | |
785 | - use super::{ChainValidation, Error, Results, Seal, Signature}; |
786 | + use super::{ChainValidation, Results, Seal, Signature}; |
787 | |
788 | use crate::common::parse::*; |
789 | |
790 | @@ -13,7 +14,7 @@ pub(crate) const CV: u16 = (b'c' as u16) | ((b'v' as u16) << 8); |
791 | |
792 | impl<'x> Signature<'x> { |
793 | #[allow(clippy::while_let_on_iterator)] |
794 | - pub fn parse(header: &'_ [u8]) -> super::Result<Self> { |
795 | + pub fn parse(header: &'_ [u8]) -> crate::Result<Self> { |
796 | let mut signature = Signature { |
797 | a: Algorithm::RsaSha256, |
798 | d: (b""[..]).into(), |
799 | @@ -37,7 +38,7 @@ impl<'x> Signature<'x> { |
800 | I => { |
801 | signature.i = header.number().unwrap_or(0) as u32; |
802 | if !(1..=50).contains(&signature.i) { |
803 | - return Err(Error::InvalidInstance); |
804 | + return Err(Error::ARCInvalidInstance); |
805 | } |
806 | } |
807 | A => { |
808 | @@ -82,7 +83,7 @@ impl<'x> Signature<'x> { |
809 | |
810 | impl<'x> Seal<'x> { |
811 | #[allow(clippy::while_let_on_iterator)] |
812 | - pub fn parse(header: &'_ [u8]) -> super::Result<Self> { |
813 | + pub fn parse(header: &'_ [u8]) -> crate::Result<Self> { |
814 | let mut seal = Seal { |
815 | a: Algorithm::RsaSha256, |
816 | d: (b""[..]).into(), |
817 | @@ -122,22 +123,22 @@ impl<'x> Seal<'x> { |
818 | b'p' | b'P' if header.match_bytes(b"ass") => { |
819 | cv = ChainValidation::Pass.into(); |
820 | } |
821 | - _ => return Err(Error::InvalidChainValidation), |
822 | + _ => return Err(Error::ARCInvalidCV), |
823 | } |
824 | if !header.seek_tag_end() { |
825 | - return Err(Error::InvalidChainValidation); |
826 | + return Err(Error::ARCInvalidCV); |
827 | } |
828 | } |
829 | H => { |
830 | - return Err(Error::HasHeaderTag); |
831 | + return Err(Error::ARCHasHeaderTag); |
832 | } |
833 | _ => header.ignore(), |
834 | } |
835 | } |
836 | - seal.cv = cv.ok_or(Error::InvalidChainValidation)?; |
837 | + seal.cv = cv.ok_or(Error::ARCInvalidCV)?; |
838 | |
839 | if !(1..=50).contains(&seal.i) { |
840 | - Err(Error::InvalidInstance) |
841 | + Err(Error::ARCInvalidInstance) |
842 | } else if !seal.d.is_empty() && !seal.s.is_empty() && !seal.b.is_empty() { |
843 | Ok(seal) |
844 | } else { |
845 | @@ -148,9 +149,8 @@ impl<'x> Seal<'x> { |
846 | |
847 | impl Results { |
848 | #[allow(clippy::while_let_on_iterator)] |
849 | - pub fn parse(header: &'_ [u8]) -> super::Result<Self> { |
850 | + pub fn parse(header: &'_ [u8]) -> crate::Result<Self> { |
851 | let mut results = Results { i: 0 }; |
852 | - let header_len = header.len(); |
853 | let mut header = header.iter(); |
854 | |
855 | while let Some(key) = header.key() { |
856 | @@ -166,7 +166,7 @@ impl Results { |
857 | if (1..=50).contains(&results.i) { |
858 | Ok(results) |
859 | } else { |
860 | - Err(Error::InvalidInstance) |
861 | + Err(Error::ARCInvalidInstance) |
862 | } |
863 | } |
864 | } |
865 | diff --git a/src/arc/verify.rs b/src/arc/verify.rs |
866 | deleted file mode 100644 |
867 | index e69de29..0000000 |
868 | --- a/src/arc/verify.rs |
869 | +++ /dev/null |
870 | diff --git a/src/common/message.rs b/src/common/message.rs |
871 | index 63111e8..0764e75 100644 |
872 | --- a/src/common/message.rs |
873 | +++ b/src/common/message.rs |
874 | @@ -1,36 +1,41 @@ |
875 | - use std::borrow::Cow; |
876 | + use std::time::SystemTime; |
877 | |
878 | use mail_parser::{parsers::MessageStream, HeaderValue}; |
879 | + use sha1::Sha1; |
880 | + use sha2::Sha256; |
881 | |
882 | use crate::{ |
883 | - arc::{self, ChainValidation, Seal, Set}, |
884 | - dkim::{self, Canonicalization, HashAlgorithm}, |
885 | + arc::{self, ChainValidation, Set}, |
886 | + dkim::{self, Algorithm, HashAlgorithm}, |
887 | + Error, |
888 | }; |
889 | |
890 | - use super::headers::{AuthenticatedHeader, Header, HeaderParser}; |
891 | - |
892 | - pub struct AuthenticatedMessage<'x> { |
893 | - pub(crate) headers: Vec<(&'x [u8], &'x [u8])>, |
894 | - pub(crate) from: Vec<Cow<'x, str>>, |
895 | - pub(crate) body: &'x [u8], |
896 | - pub(crate) body_hashes: Vec<(Canonicalization, HashAlgorithm, u64, Vec<u8>)>, |
897 | - pub(crate) failed: Vec<Header<'x, arc::Error>>, |
898 | - pub(crate) dkim_headers: Vec<Header<'x, dkim::Signature<'x>>>, |
899 | - pub(crate) arc_sets: Vec<Set<'x>>, |
900 | - pub(crate) cv: ChainValidation, |
901 | - } |
902 | + use super::{ |
903 | + headers::{AuthenticatedHeader, Header, HeaderParser}, |
904 | + AuthPhase, AuthResult, AuthenticatedMessage, |
905 | + }; |
906 | |
907 | impl<'x> AuthenticatedMessage<'x> { |
908 | + #[inline(always)] |
909 | pub fn new(raw_message: &'x [u8]) -> Option<Self> { |
910 | + Self::new_( |
911 | + raw_message, |
912 | + SystemTime::now() |
913 | + .duration_since(SystemTime::UNIX_EPOCH) |
914 | + .map(|d| d.as_secs()) |
915 | + .unwrap_or(0), |
916 | + ) |
917 | + } |
918 | + |
919 | + pub(crate) fn new_(raw_message: &'x [u8], now: u64) -> Option<Self> { |
920 | let mut message = AuthenticatedMessage { |
921 | headers: Vec::new(), |
922 | from: Vec::new(), |
923 | - body: raw_message, |
924 | - body_hashes: Vec::new(), |
925 | - failed: Vec::new(), |
926 | dkim_headers: Vec::new(), |
927 | arc_sets: Vec::new(), |
928 | - cv: ChainValidation::None, |
929 | + arc_result: AuthResult::None, |
930 | + dkim_result: AuthResult::None, |
931 | + phase: AuthPhase::Done, |
932 | }; |
933 | |
934 | let mut ams_headers = Vec::new(); |
935 | @@ -38,16 +43,27 @@ impl<'x> AuthenticatedMessage<'x> { |
936 | let mut aar_headers = Vec::new(); |
937 | |
938 | let mut headers = HeaderParser::new(raw_message); |
939 | + let mut dkim_headers = Vec::new(); |
940 | |
941 | for (header, value) in &mut headers { |
942 | let name = match header { |
943 | AuthenticatedHeader::Ds(name) => { |
944 | match dkim::Signature::parse(value) { |
945 | - Ok(s) => { |
946 | - message.dkim_headers.push(Header::new(name, value, s)); |
947 | + Ok(signature) => { |
948 | + if signature.x == 0 || (signature.x > signature.t && signature.x > now) |
949 | + { |
950 | + dkim_headers.push(Header::new(name, value, signature)); |
951 | + } else { |
952 | + message.dkim_result = AuthResult::PermFail(Header::new( |
953 | + name, |
954 | + value, |
955 | + crate::Error::SignatureExpired, |
956 | + )); |
957 | + } |
958 | } |
959 | Err(err) => { |
960 | - message.failed.push(Header::new(name, value, err.into())); |
961 | + message.dkim_result = |
962 | + AuthResult::PermFail(Header::new(name, value, err)); |
963 | } |
964 | } |
965 | |
966 | @@ -59,7 +75,8 @@ impl<'x> AuthenticatedMessage<'x> { |
967 | aar_headers.push(Header::new(name, value, r)); |
968 | } |
969 | Err(err) => { |
970 | - message.failed.push(Header::new(name, value, err)); |
971 | + message.arc_result = |
972 | + AuthResult::PermFail(Header::new(name, value, err)); |
973 | } |
974 | } |
975 | |
976 | @@ -71,7 +88,8 @@ impl<'x> AuthenticatedMessage<'x> { |
977 | ams_headers.push(Header::new(name, value, s)); |
978 | } |
979 | Err(err) => { |
980 | - message.failed.push(Header::new(name, value, err)); |
981 | + message.arc_result = |
982 | + AuthResult::PermFail(Header::new(name, value, err)); |
983 | } |
984 | } |
985 | |
986 | @@ -83,7 +101,8 @@ impl<'x> AuthenticatedMessage<'x> { |
987 | as_headers.push(Header::new(name, value, s)); |
988 | } |
989 | Err(err) => { |
990 | - message.failed.push(Header::new(name, value, err)); |
991 | + message.arc_result = |
992 | + AuthResult::PermFail(Header::new(name, value, err)); |
993 | } |
994 | } |
995 | name |
996 | @@ -123,6 +142,17 @@ impl<'x> AuthenticatedMessage<'x> { |
997 | message.headers.push((name, value)); |
998 | } |
999 | |
1000 | + if message.headers.is_empty() { |
1001 | + return None; |
1002 | + } |
1003 | + |
1004 | + // Obtain message body |
1005 | + let body = headers |
1006 | + .body_offset() |
1007 | + .and_then(|pos| raw_message.get(pos..)) |
1008 | + .unwrap_or_default(); |
1009 | + let mut body_hashes = Vec::new(); |
1010 | + |
1011 | // Group ARC headers in sets |
1012 | let arc_headers = ams_headers.len(); |
1013 | if (1..=50).contains(&arc_headers) |
1014 | @@ -132,7 +162,6 @@ impl<'x> AuthenticatedMessage<'x> { |
1015 | as_headers.sort_unstable_by(|a, b| a.header.i.cmp(&b.header.i)); |
1016 | ams_headers.sort_unstable_by(|a, b| a.header.i.cmp(&b.header.i)); |
1017 | aar_headers.sort_unstable_by(|a, b| a.header.i.cmp(&b.header.i)); |
1018 | - let mut success = true; |
1019 | |
1020 | for (pos, ((seal, signature), results)) in as_headers |
1021 | .into_iter() |
1022 | @@ -140,64 +169,133 @@ impl<'x> AuthenticatedMessage<'x> { |
1023 | .zip(aar_headers) |
1024 | .enumerate() |
1025 | { |
1026 | - if success { |
1027 | - success = (seal.header.i as usize == (pos + 1)) |
1028 | - && (signature.header.i as usize == (pos + 1)) |
1029 | - && (results.header.i as usize == (pos + 1)) |
1030 | - && ((pos == 0 && seal.header.cv == ChainValidation::None) |
1031 | - || (pos > 0 && seal.header.cv == ChainValidation::Pass)); |
1032 | - } |
1033 | - message.arc_sets.push(Set { |
1034 | - signature, |
1035 | - seal, |
1036 | - results, |
1037 | - }); |
1038 | - } |
1039 | + if (seal.header.i as usize == (pos + 1)) |
1040 | + && (signature.header.i as usize == (pos + 1)) |
1041 | + && (results.header.i as usize == (pos + 1)) |
1042 | + && ((pos == 0 && seal.header.cv == ChainValidation::None) |
1043 | + || (pos > 0 && seal.header.cv == ChainValidation::Pass)) |
1044 | + { |
1045 | + // Validate last signature in the chain |
1046 | + if pos == arc_headers - 1 { |
1047 | + // Validate expiration |
1048 | + let signature_ = &signature.header; |
1049 | + if signature_.x > 0 && (signature_.x < signature_.t || signature_.x < now) { |
1050 | + message.arc_result = AuthResult::PermFail(Header::new( |
1051 | + signature.name, |
1052 | + signature.value, |
1053 | + Error::SignatureExpired, |
1054 | + )); |
1055 | + break; |
1056 | + } |
1057 | |
1058 | - if !success { |
1059 | - for set in message.arc_sets.drain(..) { |
1060 | - for (name, value) in [ |
1061 | - (set.signature.name, set.signature.value), |
1062 | - (set.seal.name, set.seal.value), |
1063 | - (set.results.name, set.results.value), |
1064 | - ] { |
1065 | - message |
1066 | - .failed |
1067 | - .push(Header::new(name, value, arc::Error::BrokenArcChain)); |
1068 | + // Validate body hash |
1069 | + let bh = match signature_.a { |
1070 | + Algorithm::RsaSha256 | Algorithm::Ed25519Sha256 => { |
1071 | + signature_.cb.hash_body::<Sha256>(body, signature_.l) |
1072 | + } |
1073 | + Algorithm::RsaSha1 => { |
1074 | + signature_.cb.hash_body::<Sha1>(body, signature_.l) |
1075 | + } |
1076 | + } |
1077 | + .unwrap_or_default(); |
1078 | + |
1079 | + let success = bh == signature_.bh; |
1080 | + body_hashes.push(( |
1081 | + signature_.cb, |
1082 | + HashAlgorithm::from(signature_.a), |
1083 | + signature_.l, |
1084 | + bh, |
1085 | + )); |
1086 | + |
1087 | + if !success { |
1088 | + message.arc_result = AuthResult::PermFail(Header::new( |
1089 | + signature.name, |
1090 | + signature.value, |
1091 | + Error::FailedBodyHashMatch, |
1092 | + )); |
1093 | + break; |
1094 | + } |
1095 | } |
1096 | + |
1097 | + message.arc_sets.push(Set { |
1098 | + signature, |
1099 | + seal, |
1100 | + results, |
1101 | + }); |
1102 | + } else { |
1103 | + message.arc_result = AuthResult::PermFail(Header::new( |
1104 | + signature.name, |
1105 | + signature.value, |
1106 | + Error::ARCBrokenChain, |
1107 | + )); |
1108 | + break; |
1109 | } |
1110 | - message.cv = ChainValidation::Fail; |
1111 | } |
1112 | - } else if arc_headers > 0 { |
1113 | + } else if arc_headers > 0 && message.arc_result == AuthResult::None { |
1114 | // Missing ARC headers, fail all. |
1115 | - message.failed.extend( |
1116 | - ams_headers |
1117 | - .into_iter() |
1118 | - .map(|h| Header::new(h.name, h.value, arc::Error::BrokenArcChain)) |
1119 | - .chain( |
1120 | - as_headers |
1121 | - .into_iter() |
1122 | - .map(|h| Header::new(h.name, h.value, arc::Error::BrokenArcChain)), |
1123 | - ) |
1124 | - .chain( |
1125 | - aar_headers |
1126 | - .into_iter() |
1127 | - .map(|h| Header::new(h.name, h.value, arc::Error::BrokenArcChain)), |
1128 | - ), |
1129 | - ); |
1130 | - message.cv = ChainValidation::Fail; |
1131 | + let header = ams_headers |
1132 | + .into_iter() |
1133 | + .map(|h| Header::new(h.name, h.value, Error::ARCBrokenChain)) |
1134 | + .chain( |
1135 | + as_headers |
1136 | + .into_iter() |
1137 | + .map(|h| Header::new(h.name, h.value, Error::ARCBrokenChain)), |
1138 | + ) |
1139 | + .chain( |
1140 | + aar_headers |
1141 | + .into_iter() |
1142 | + .map(|h| Header::new(h.name, h.value, Error::ARCBrokenChain)), |
1143 | + ) |
1144 | + .next() |
1145 | + .unwrap(); |
1146 | + message.arc_result = AuthResult::PermFail(header); |
1147 | } |
1148 | |
1149 | - message.body = headers |
1150 | - .body_offset() |
1151 | - .and_then(|pos| raw_message.get(pos..)) |
1152 | - .unwrap_or_default(); |
1153 | + // Validate body hash of DKIM signatures |
1154 | + if !dkim_headers.is_empty() { |
1155 | + message.dkim_headers = Vec::with_capacity(dkim_headers.len()); |
1156 | + for header in dkim_headers { |
1157 | + let signature = &header.header; |
1158 | + let ha = HashAlgorithm::from(signature.a); |
1159 | |
1160 | - if !message.headers.is_empty() { |
1161 | - message.into() |
1162 | - } else { |
1163 | - None |
1164 | + let bh = if let Some((_, _, _, bh)) = body_hashes |
1165 | + .iter() |
1166 | + .find(|(c, h, l, _)| c == &signature.cb && h == &ha && l == &signature.l) |
1167 | + { |
1168 | + bh |
1169 | + } else { |
1170 | + let bh = match signature.a { |
1171 | + Algorithm::RsaSha256 | Algorithm::Ed25519Sha256 => { |
1172 | + signature.cb.hash_body::<Sha256>(body, signature.l) |
1173 | + } |
1174 | + Algorithm::RsaSha1 => signature.cb.hash_body::<Sha1>(body, signature.l), |
1175 | + } |
1176 | + .unwrap_or_default(); |
1177 | + |
1178 | + body_hashes.push((signature.cb, ha, signature.l, bh)); |
1179 | + &body_hashes.last().unwrap().3 |
1180 | + }; |
1181 | + |
1182 | + if bh == &signature.bh { |
1183 | + message.dkim_headers.push(header); |
1184 | + } else { |
1185 | + message.dkim_result = AuthResult::PermFail(Header::new( |
1186 | + header.name, |
1187 | + header.value, |
1188 | + crate::Error::FailedBodyHashMatch, |
1189 | + )); |
1190 | + } |
1191 | + } |
1192 | + } |
1193 | + |
1194 | + if !message.dkim_headers.is_empty() { |
1195 | + message.dkim_headers.reverse(); |
1196 | + message.phase = AuthPhase::Dkim; |
1197 | + } else if !message.arc_sets.is_empty() && message.arc_result == AuthResult::None { |
1198 | + message.phase = AuthPhase::Ams; |
1199 | } |
1200 | + |
1201 | + message.into() |
1202 | } |
1203 | } |
1204 | |
1205 | diff --git a/src/common/mod.rs b/src/common/mod.rs |
1206 | index e0bff3e..930c63f 100644 |
1207 | --- a/src/common/mod.rs |
1208 | +++ b/src/common/mod.rs |
1209 | @@ -1,3 +1,40 @@ |
1210 | + use std::borrow::Cow; |
1211 | + |
1212 | + use crate::{ |
1213 | + arc::Set, |
1214 | + dkim::{self}, |
1215 | + }; |
1216 | + |
1217 | + use self::headers::Header; |
1218 | + |
1219 | pub mod headers; |
1220 | pub mod message; |
1221 | pub mod parse; |
1222 | + pub mod verify; |
1223 | + |
1224 | + #[derive(Debug, Clone)] |
1225 | + pub struct AuthenticatedMessage<'x> { |
1226 | + pub(crate) headers: Vec<(&'x [u8], &'x [u8])>, |
1227 | + pub(crate) from: Vec<Cow<'x, str>>, |
1228 | + pub(crate) dkim_headers: Vec<Header<'x, dkim::Signature<'x>>>, |
1229 | + pub(crate) arc_sets: Vec<Set<'x>>, |
1230 | + pub(crate) arc_result: AuthResult<'x, ()>, |
1231 | + pub(crate) dkim_result: AuthResult<'x, dkim::Signature<'x>>, |
1232 | + pub(crate) phase: AuthPhase, |
1233 | + } |
1234 | + |
1235 | + #[derive(Debug, PartialEq, Eq, Clone)] |
1236 | + pub enum AuthPhase { |
1237 | + Dkim, |
1238 | + Ams, |
1239 | + As(usize), |
1240 | + Done, |
1241 | + } |
1242 | + |
1243 | + #[derive(Debug, PartialEq, Eq, Clone)] |
1244 | + pub enum AuthResult<'x, T> { |
1245 | + None, |
1246 | + PermFail(Header<'x, crate::Error>), |
1247 | + TempFail(Header<'x, crate::Error>), |
1248 | + Pass(T), |
1249 | + } |
1250 | diff --git a/src/common/verify.rs b/src/common/verify.rs |
1251 | new file mode 100644 |
1252 | index 0000000..16fa632 |
1253 | --- /dev/null |
1254 | +++ b/src/common/verify.rs |
1255 | @@ -0,0 +1,355 @@ |
1256 | + use std::borrow::Cow; |
1257 | + |
1258 | + use rsa::PaddingScheme; |
1259 | + use sha1::Sha1; |
1260 | + use sha2::Sha256; |
1261 | + |
1262 | + use crate::{ |
1263 | + dkim::{ |
1264 | + self, parse::TryIntoRecord, verify::Verifier, Algorithm, Canonicalization, Flag, PublicKey, |
1265 | + Record, |
1266 | + }, |
1267 | + Error, |
1268 | + }; |
1269 | + |
1270 | + use super::{headers::Header, AuthPhase, AuthResult, AuthenticatedMessage}; |
1271 | + |
1272 | + impl<'x> AuthenticatedMessage<'x> { |
1273 | + pub fn verify(&mut self, maybe_record: impl TryIntoRecord<'x>) { |
1274 | + let maybe_record = maybe_record.try_into_record(); |
1275 | + |
1276 | + match self.phase { |
1277 | + AuthPhase::Dkim => { |
1278 | + let header = self.dkim_headers.pop().unwrap(); |
1279 | + let record = match maybe_record { |
1280 | + Ok(record) => record, |
1281 | + Err(err) => { |
1282 | + self.set_dkim_error(header, err); |
1283 | + return; |
1284 | + } |
1285 | + }; |
1286 | + let signature = &header.header; |
1287 | + |
1288 | + // Enforce t=s flag |
1289 | + if !record.validate_auid(&signature.i, &signature.d) { |
1290 | + self.set_dkim_error(header, Error::FailedAUIDMatch); |
1291 | + return; |
1292 | + } |
1293 | + |
1294 | + // Hash headers |
1295 | + let dkim_hdr_value = header.value.strip_signature(); |
1296 | + let headers = self.signed_headers(&signature.h, header.name, &dkim_hdr_value); |
1297 | + let hh = match signature.a { |
1298 | + Algorithm::RsaSha256 | Algorithm::Ed25519Sha256 => { |
1299 | + signature.ch.hash_headers::<Sha256>(headers) |
1300 | + } |
1301 | + Algorithm::RsaSha1 => signature.ch.hash_headers::<Sha1>(headers), |
1302 | + } |
1303 | + .unwrap_or_default(); |
1304 | + |
1305 | + // Verify signature |
1306 | + match signature.verify(record.as_ref(), &hh) { |
1307 | + Ok(_) => { |
1308 | + self.dkim_result = AuthResult::Pass(header.header); |
1309 | + self.phase = if !self.arc_sets.is_empty() { |
1310 | + AuthPhase::Ams |
1311 | + } else { |
1312 | + AuthPhase::Done |
1313 | + }; |
1314 | + } |
1315 | + Err(err) => { |
1316 | + self.set_dkim_error(header, err); |
1317 | + } |
1318 | + } |
1319 | + } |
1320 | + AuthPhase::Ams => { |
1321 | + let header = &self.arc_sets.last().unwrap().signature; |
1322 | + let record = match maybe_record { |
1323 | + Ok(record) => record, |
1324 | + Err(err) => { |
1325 | + self.set_arc_error(header.name, header.value, err); |
1326 | + return; |
1327 | + } |
1328 | + }; |
1329 | + let signature = &header.header; |
1330 | + |
1331 | + // Hash headers |
1332 | + let dkim_hdr_value = header.value.strip_signature(); |
1333 | + let headers = self.signed_headers(&signature.h, header.name, &dkim_hdr_value); |
1334 | + let hh = match signature.a { |
1335 | + Algorithm::RsaSha256 | Algorithm::Ed25519Sha256 => { |
1336 | + signature.ch.hash_headers::<Sha256>(headers) |
1337 | + } |
1338 | + Algorithm::RsaSha1 => signature.ch.hash_headers::<Sha1>(headers), |
1339 | + } |
1340 | + .unwrap_or_default(); |
1341 | + |
1342 | + // Verify signature |
1343 | + match signature.verify(record.as_ref(), &hh) { |
1344 | + Ok(_) => { |
1345 | + self.phase = AuthPhase::As(self.arc_sets.len() - 1); |
1346 | + } |
1347 | + Err(err) => { |
1348 | + self.set_arc_error(header.name, header.value, err); |
1349 | + } |
1350 | + } |
1351 | + } |
1352 | + AuthPhase::As(pos) => { |
1353 | + let header = &self.arc_sets[pos].seal; |
1354 | + let record = match maybe_record { |
1355 | + Ok(record) => record, |
1356 | + Err(err) => { |
1357 | + self.set_arc_error(header.name, header.value, err); |
1358 | + return; |
1359 | + } |
1360 | + }; |
1361 | + let seal = &header.header; |
1362 | + |
1363 | + // Build seal headers |
1364 | + let cur_set = &self.arc_sets[pos]; |
1365 | + let seal_signature = cur_set.seal.value.strip_signature(); |
1366 | + let headers = self |
1367 | + .arc_sets |
1368 | + .iter() |
1369 | + .take(pos) |
1370 | + .flat_map(|set| { |
1371 | + [ |
1372 | + (set.results.name, set.results.value), |
1373 | + (set.signature.name, set.signature.value), |
1374 | + (set.seal.name, set.seal.value), |
1375 | + ] |
1376 | + }) |
1377 | + .chain([ |
1378 | + (cur_set.results.name, cur_set.results.value), |
1379 | + (cur_set.signature.name, cur_set.signature.value), |
1380 | + (cur_set.seal.name, &seal_signature), |
1381 | + ]); |
1382 | + |
1383 | + /*let mut headers = Vec::with_capacity((pos + 1) * 3); |
1384 | + for set in self.arc_sets.iter().take(pos + 1) { |
1385 | + headers.push((set.results.name, Cow::from(set.results.value))); |
1386 | + headers.push((set.signature.name, Cow::from(set.signature.value))); |
1387 | + headers.push((set.seal.name, Cow::from(set.seal.value.strip_signature()))); |
1388 | + } |
1389 | + let headers_iter = headers.iter().map(|(h, v)| (*h, v.as_ref()));*/ |
1390 | + |
1391 | + let hh = match seal.a { |
1392 | + Algorithm::RsaSha256 | Algorithm::Ed25519Sha256 => { |
1393 | + Canonicalization::Relaxed.hash_headers::<Sha256>(headers) |
1394 | + } |
1395 | + Algorithm::RsaSha1 => Canonicalization::Relaxed.hash_headers::<Sha1>(headers), |
1396 | + } |
1397 | + .unwrap_or_default(); |
1398 | + |
1399 | + // Verify ARC seal |
1400 | + match seal.verify(record.as_ref(), &hh) { |
1401 | + Ok(_) => { |
1402 | + if pos > 0 { |
1403 | + self.phase = AuthPhase::As(pos - 1); |
1404 | + } else { |
1405 | + self.arc_result = AuthResult::Pass(()); |
1406 | + self.phase = AuthPhase::Done; |
1407 | + } |
1408 | + } |
1409 | + Err(err) => { |
1410 | + self.set_arc_error(header.name, header.value, err); |
1411 | + } |
1412 | + } |
1413 | + } |
1414 | + AuthPhase::Done => (), |
1415 | + } |
1416 | + } |
1417 | + |
1418 | + fn set_dkim_error(&mut self, header: Header<'x, dkim::Signature>, err: Error) { |
1419 | + let header = Header::new(header.name, header.value, err); |
1420 | + self.dkim_result = if header.header != Error::DNSFailure { |
1421 | + AuthResult::PermFail(header) |
1422 | + } else { |
1423 | + AuthResult::TempFail(header) |
1424 | + }; |
1425 | + if self.dkim_headers.is_empty() { |
1426 | + self.phase = if !self.arc_sets.is_empty() { |
1427 | + AuthPhase::Ams |
1428 | + } else { |
1429 | + AuthPhase::Done |
1430 | + }; |
1431 | + } |
1432 | + } |
1433 | + |
1434 | + fn set_arc_error(&mut self, name: &'x [u8], value: &'x [u8], err: Error) { |
1435 | + self.arc_result = if err != Error::DNSFailure { |
1436 | + AuthResult::PermFail(Header::new(name, value, err)) |
1437 | + } else { |
1438 | + AuthResult::TempFail(Header::new(name, value, err)) |
1439 | + }; |
1440 | + self.phase = AuthPhase::Done; |
1441 | + } |
1442 | + |
1443 | + pub fn next_entry(&self) -> Option<String> { |
1444 | + let (s, d) = match self.phase { |
1445 | + AuthPhase::Dkim => { |
1446 | + let s = &self.dkim_headers.last().unwrap().header; |
1447 | + (s.s.as_ref(), s.d.as_ref()) |
1448 | + } |
1449 | + AuthPhase::Ams => { |
1450 | + let s = &self.arc_sets.last().unwrap().signature.header; |
1451 | + (s.s.as_ref(), s.d.as_ref()) |
1452 | + } |
1453 | + AuthPhase::As(pos) => { |
1454 | + let s = &self.arc_sets[pos].seal.header; |
1455 | + (s.s.as_ref(), s.d.as_ref()) |
1456 | + } |
1457 | + AuthPhase::Done => return None, |
1458 | + }; |
1459 | + |
1460 | + format!( |
1461 | + "{}._domainkey.{}", |
1462 | + std::str::from_utf8(s).unwrap_or_default(), |
1463 | + std::str::from_utf8(d).unwrap_or_default() |
1464 | + ) |
1465 | + .into() |
1466 | + } |
1467 | + } |
1468 | + |
1469 | + pub(crate) trait VerifySignature { |
1470 | + fn b(&self) -> &[u8]; |
1471 | + fn a(&self) -> Algorithm; |
1472 | + fn verify(&self, record: &Record, hh: &[u8]) -> crate::Result<()> { |
1473 | + match (&self.a(), &record.p) { |
1474 | + (Algorithm::RsaSha256, PublicKey::Rsa(public_key)) => rsa::PublicKey::verify( |
1475 | + public_key, |
1476 | + PaddingScheme::new_pkcs1v15_sign::<Sha256>(), |
1477 | + hh, |
1478 | + self.b(), |
1479 | + ) |
1480 | + .map_err(|_| Error::FailedVerification), |
1481 | + |
1482 | + (Algorithm::RsaSha1, PublicKey::Rsa(public_key)) => rsa::PublicKey::verify( |
1483 | + public_key, |
1484 | + PaddingScheme::new_pkcs1v15_sign::<Sha1>(), |
1485 | + hh, |
1486 | + self.b(), |
1487 | + ) |
1488 | + .map_err(|_| Error::FailedVerification), |
1489 | + |
1490 | + (Algorithm::Ed25519Sha256, PublicKey::Ed25519(public_key)) => public_key |
1491 | + .verify_strict( |
1492 | + hh, |
1493 | + &ed25519_dalek::Signature::from_bytes(self.b()) |
1494 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
1495 | + ) |
1496 | + .map_err(|_| Error::FailedVerification), |
1497 | + |
1498 | + (_, PublicKey::Revoked) => Err(Error::RevokedPublicKey), |
1499 | + |
1500 | + (_, _) => Err(Error::IncompatibleAlgorithms), |
1501 | + } |
1502 | + } |
1503 | + } |
1504 | + |
1505 | + impl Record { |
1506 | + #[allow(clippy::while_let_on_iterator)] |
1507 | + pub fn validate_auid(&self, i: &[u8], d: &[u8]) -> bool { |
1508 | + // Enforce t=s flag |
1509 | + if !i.is_empty() && self.has_flag(Flag::MatchDomain) { |
1510 | + let mut auid = i.as_ref().iter(); |
1511 | + let mut domain = d.as_ref().iter(); |
1512 | + while let Some(&ch) = auid.next() { |
1513 | + if ch == b'@' { |
1514 | + break; |
1515 | + } |
1516 | + } |
1517 | + while let Some(ch) = auid.next() { |
1518 | + if let Some(dch) = domain.next() { |
1519 | + if !ch.eq_ignore_ascii_case(dch) { |
1520 | + return false; |
1521 | + } |
1522 | + } else { |
1523 | + break; |
1524 | + } |
1525 | + } |
1526 | + if domain.next().is_some() { |
1527 | + return false; |
1528 | + } |
1529 | + } |
1530 | + |
1531 | + true |
1532 | + } |
1533 | + } |
1534 | + |
1535 | + #[cfg(test)] |
1536 | + mod test { |
1537 | + use std::{collections::HashMap, fs, path::PathBuf}; |
1538 | + |
1539 | + use crate::common::{AuthResult, AuthenticatedMessage}; |
1540 | + |
1541 | + #[test] |
1542 | + fn dkim_verify() { |
1543 | + let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); |
1544 | + test_dir.push("resources"); |
1545 | + test_dir.push("dkim"); |
1546 | + |
1547 | + for file_name in fs::read_dir(&test_dir).unwrap() { |
1548 | + let file_name = file_name.unwrap().path(); |
1549 | + /*if !file_name.to_str().unwrap().contains("002") { |
1550 | + continue; |
1551 | + }*/ |
1552 | + println!("file {}", file_name.to_str().unwrap()); |
1553 | + |
1554 | + let test = String::from_utf8(fs::read(&file_name).unwrap()).unwrap(); |
1555 | + let (dns_records, message) = test.split_once("\n\n").unwrap(); |
1556 | + let dns_records = dns_records |
1557 | + .split('\n') |
1558 | + .filter_map(|r| r.split_once(' ').map(|(a, b)| (a, b.as_bytes()))) |
1559 | + .collect::<HashMap<_, _>>(); |
1560 | + let message = message.replace('\n', "\r\n"); |
1561 | + |
1562 | + let mut verifier = AuthenticatedMessage::new_(message.as_bytes(), 1667843664).unwrap(); |
1563 | + while let Some(domain) = verifier.next_entry() { |
1564 | + verifier.verify(*dns_records.get(domain.as_str()).unwrap()); |
1565 | + } |
1566 | + assert!( |
1567 | + matches!(verifier.dkim_result, AuthResult::Pass(_)), |
1568 | + "Failed: {:?}", |
1569 | + verifier.dkim_result |
1570 | + ); |
1571 | + } |
1572 | + } |
1573 | + |
1574 | + #[test] |
1575 | + fn arc_verify() { |
1576 | + let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); |
1577 | + test_dir.push("resources"); |
1578 | + test_dir.push("arc"); |
1579 | + |
1580 | + for file_name in fs::read_dir(&test_dir).unwrap() { |
1581 | + let file_name = file_name.unwrap().path(); |
1582 | + if !file_name.to_str().unwrap().contains("002") { |
1583 | + continue; |
1584 | + } |
1585 | + println!("file {}", file_name.to_str().unwrap()); |
1586 | + |
1587 | + let test = String::from_utf8(fs::read(&file_name).unwrap()).unwrap(); |
1588 | + let (dns_records, message) = test.split_once("\n\n").unwrap(); |
1589 | + let dns_records = dns_records |
1590 | + .split('\n') |
1591 | + .filter_map(|r| r.split_once(' ').map(|(a, b)| (a, b.as_bytes()))) |
1592 | + .collect::<HashMap<_, _>>(); |
1593 | + let message = message.replace('\n', "\r\n"); |
1594 | + |
1595 | + let mut verifier = AuthenticatedMessage::new_(message.as_bytes(), 1667843664).unwrap(); |
1596 | + while let Some(domain) = verifier.next_entry() { |
1597 | + verifier.verify(*dns_records.get(domain.as_str()).unwrap()); |
1598 | + } |
1599 | + |
1600 | + println!("DKIM: {:?}", verifier.dkim_result); |
1601 | + println!("ARC: {:?}", verifier.arc_result); |
1602 | + |
1603 | + /*assert!( |
1604 | + matches!(verifier.dkim_result, AuthResult::Pass(_)), |
1605 | + "Failed: {:?}", |
1606 | + verifier.dkim_result |
1607 | + );*/ |
1608 | + } |
1609 | + } |
1610 | + } |
1611 | diff --git a/src/dkim/canonicalize.rs b/src/dkim/canonicalize.rs |
1612 | index 62d2681..79152fa 100644 |
1613 | --- a/src/dkim/canonicalize.rs |
1614 | +++ b/src/dkim/canonicalize.rs |
1615 | @@ -11,19 +11,21 @@ |
1616 | |
1617 | use std::io::Write; |
1618 | |
1619 | + use sha1::Digest; |
1620 | + |
1621 | use crate::common::headers::HeaderIterator; |
1622 | |
1623 | use super::{Canonicalization, DKIMSigner}; |
1624 | |
1625 | impl Canonicalization { |
1626 | - pub fn canonicalize_body(&self, message: &[u8], mut hasher: impl Write) -> std::io::Result<()> { |
1627 | + pub fn canonicalize_body(&self, body: &[u8], mut hasher: impl Write) -> std::io::Result<()> { |
1628 | let mut crlf_seq = 0; |
1629 | |
1630 | match self { |
1631 | Canonicalization::Relaxed => { |
1632 | let mut last_ch = 0; |
1633 | |
1634 | - for &ch in message { |
1635 | + for &ch in body { |
1636 | match ch { |
1637 | b' ' | b'\t' => { |
1638 | while crlf_seq > 0 { |
1639 | @@ -53,7 +55,7 @@ impl Canonicalization { |
1640 | } |
1641 | } |
1642 | Canonicalization::Simple => { |
1643 | - for &ch in message { |
1644 | + for &ch in body { |
1645 | match ch { |
1646 | b'\n' => { |
1647 | crlf_seq += 1; |
1648 | @@ -117,6 +119,34 @@ impl Canonicalization { |
1649 | Ok(()) |
1650 | } |
1651 | |
1652 | + pub fn hash_headers<'x, T>( |
1653 | + &self, |
1654 | + headers: impl Iterator<Item = (&'x [u8], &'x [u8])>, |
1655 | + ) -> std::io::Result<Vec<u8>> |
1656 | + where |
1657 | + T: Digest + std::io::Write, |
1658 | + { |
1659 | + let mut hasher = T::new(); |
1660 | + self.canonicalize_headers(headers, &mut hasher)?; |
1661 | + Ok(hasher.finalize().to_vec()) |
1662 | + } |
1663 | + |
1664 | + pub fn hash_body<T>(&self, body: &[u8], l: u64) -> std::io::Result<Vec<u8>> |
1665 | + where |
1666 | + T: Digest + std::io::Write, |
1667 | + { |
1668 | + let mut hasher = T::new(); |
1669 | + self.canonicalize_body( |
1670 | + if l == 0 || body.is_empty() { |
1671 | + body |
1672 | + } else { |
1673 | + &body[..std::cmp::min(l as usize, body.len())] |
1674 | + }, |
1675 | + &mut hasher, |
1676 | + )?; |
1677 | + Ok(hasher.finalize().to_vec()) |
1678 | + } |
1679 | + |
1680 | pub fn serialize_name(&self, mut writer: impl Write) -> std::io::Result<()> { |
1681 | writer.write_all(match self { |
1682 | Canonicalization::Relaxed => b"relaxed", |
1683 | @@ -132,7 +162,7 @@ impl<'x> DKIMSigner<'x> { |
1684 | message: &[u8], |
1685 | header_hasher: impl Write, |
1686 | body_hasher: impl Write, |
1687 | - ) -> super::Result<(usize, Vec<Vec<u8>>)> { |
1688 | + ) -> crate::Result<(usize, Vec<Vec<u8>>)> { |
1689 | let mut headers_it = HeaderIterator::new(message); |
1690 | let mut headers = Vec::with_capacity(self.sign_headers.len()); |
1691 | let mut found_headers = vec![false; self.sign_headers.len()]; |
1692 | diff --git a/src/dkim/mod.rs b/src/dkim/mod.rs |
1693 | index c04d28f..9e14ffc 100644 |
1694 | --- a/src/dkim/mod.rs |
1695 | +++ b/src/dkim/mod.rs |
1696 | @@ -9,10 +9,12 @@ |
1697 | * except according to those terms. |
1698 | */ |
1699 | |
1700 | - use std::{borrow::Cow, fmt::Display}; |
1701 | + use std::borrow::Cow; |
1702 | |
1703 | use rsa::{RsaPrivateKey, RsaPublicKey}; |
1704 | |
1705 | + use crate::common::verify::VerifySignature; |
1706 | + |
1707 | pub mod canonicalize; |
1708 | pub mod parse; |
1709 | pub mod sign; |
1710 | @@ -37,40 +39,6 @@ pub enum Algorithm { |
1711 | RsaSha256, |
1712 | Ed25519Sha256, |
1713 | } |
1714 | - |
1715 | - #[derive(Debug)] |
1716 | - pub enum Error { |
1717 | - ParseError, |
1718 | - MissingParameters, |
1719 | - NoHeadersFound, |
1720 | - RSA(rsa::errors::Error), |
1721 | - PKCS(rsa::pkcs1::Error), |
1722 | - Ed25519Signature(ed25519_dalek::SignatureError), |
1723 | - Ed25519(ed25519_dalek::ed25519::Error), |
1724 | - |
1725 | - /// I/O error |
1726 | - Io(std::io::Error), |
1727 | - |
1728 | - /// Base64 decode/encode error |
1729 | - Base64, |
1730 | - |
1731 | - UnsupportedVersion, |
1732 | - UnsupportedAlgorithm, |
1733 | - UnsupportedCanonicalization, |
1734 | - |
1735 | - UnsupportedRecordVersion, |
1736 | - UnsupportedKeyType, |
1737 | - |
1738 | - FailedBodyHashMatch, |
1739 | - RevokedPublicKey, |
1740 | - IncompatibleAlgorithms, |
1741 | - FailedVerification, |
1742 | - SignatureExpired, |
1743 | - FailedAUIDMatch, |
1744 | - } |
1745 | - |
1746 | - pub type Result<T> = std::result::Result<T, Error>; |
1747 | - |
1748 | #[derive(Debug)] |
1749 | pub struct DKIMSigner<'x> { |
1750 | private_key: PrivateKey, |
1751 | @@ -87,27 +55,27 @@ pub struct DKIMSigner<'x> { |
1752 | |
1753 | #[derive(Debug, PartialEq, Eq, Clone)] |
1754 | pub struct Signature<'x> { |
1755 | - v: u32, |
1756 | - a: Algorithm, |
1757 | - d: Cow<'x, [u8]>, |
1758 | - s: Cow<'x, [u8]>, |
1759 | - b: Vec<u8>, |
1760 | - bh: Vec<u8>, |
1761 | - h: Vec<Vec<u8>>, |
1762 | - z: Vec<Vec<u8>>, |
1763 | - i: Cow<'x, [u8]>, |
1764 | - l: u64, |
1765 | - x: u64, |
1766 | - t: u64, |
1767 | - ch: Canonicalization, |
1768 | - cb: Canonicalization, |
1769 | + pub(crate) v: u32, |
1770 | + pub(crate) a: Algorithm, |
1771 | + pub(crate) d: Cow<'x, [u8]>, |
1772 | + pub(crate) s: Cow<'x, [u8]>, |
1773 | + pub(crate) b: Vec<u8>, |
1774 | + pub(crate) bh: Vec<u8>, |
1775 | + pub(crate) h: Vec<Vec<u8>>, |
1776 | + pub(crate) z: Vec<Vec<u8>>, |
1777 | + pub(crate) i: Cow<'x, [u8]>, |
1778 | + pub(crate) l: u64, |
1779 | + pub(crate) x: u64, |
1780 | + pub(crate) t: u64, |
1781 | + pub(crate) ch: Canonicalization, |
1782 | + pub(crate) cb: Canonicalization, |
1783 | } |
1784 | |
1785 | #[derive(Debug, PartialEq, Eq, Clone)] |
1786 | pub struct Record { |
1787 | - v: Version, |
1788 | - p: PublicKey, |
1789 | - f: u64, |
1790 | + pub(crate) v: Version, |
1791 | + pub(crate) p: PublicKey, |
1792 | + pub(crate) f: u64, |
1793 | } |
1794 | |
1795 | pub(crate) const R_HASH_SHA1: u64 = 0x01; |
1796 | @@ -168,46 +136,21 @@ pub(crate) enum PublicKey { |
1797 | Revoked, |
1798 | } |
1799 | |
1800 | - impl Display for Error { |
1801 | - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
1802 | - match self { |
1803 | - Error::ParseError => write!(f, "Parse error"), |
1804 | - Error::MissingParameters => write!(f, "Missing parameters"), |
1805 | - Error::NoHeadersFound => write!(f, "No headers found"), |
1806 | - Error::RSA(err) => write!(f, "RSA error: {}", err), |
1807 | - Error::PKCS(err) => write!(f, "PKCS error: {}", err), |
1808 | - Error::Io(e) => write!(f, "I/O error: {}", e), |
1809 | - Error::Base64 => write!(f, "Base64 encode or decode error."), |
1810 | - Error::UnsupportedVersion => write!(f, "Unsupported version in DKIM Signature."), |
1811 | - Error::UnsupportedAlgorithm => write!(f, "Unsupported algorithm in DKIM Signature."), |
1812 | - Error::UnsupportedCanonicalization => { |
1813 | - write!(f, "Unsupported canonicalization method in DKIM Signature.") |
1814 | - } |
1815 | - Error::UnsupportedRecordVersion => { |
1816 | - write!(f, "Unsupported version in DKIM DNS record.") |
1817 | - } |
1818 | - Error::UnsupportedKeyType => { |
1819 | - write!(f, "Unsupported key type in DKIM DNS record.") |
1820 | - } |
1821 | - Error::Ed25519Signature(err) => write!(f, "Ed25519 signature error: {}", err), |
1822 | - Error::Ed25519(err) => write!(f, "Ed25519 error: {}", err), |
1823 | - Error::FailedBodyHashMatch => { |
1824 | - write!(f, "Calculated body hash does not match signature hash.") |
1825 | - } |
1826 | - Error::RevokedPublicKey => write!(f, "Public key for this signature has been revoked."), |
1827 | - Error::IncompatibleAlgorithms => write!( |
1828 | - f, |
1829 | - "Incompatible algorithms used in signature and DKIM DNS record." |
1830 | - ), |
1831 | - Error::FailedVerification => write!(f, "Signature verification failed."), |
1832 | - Error::SignatureExpired => write!(f, "Signature expired."), |
1833 | - Error::FailedAUIDMatch => write!(f, "AUID does not match domain name."), |
1834 | + impl From<Algorithm> for HashAlgorithm { |
1835 | + fn from(a: Algorithm) -> Self { |
1836 | + match a { |
1837 | + Algorithm::RsaSha256 | Algorithm::Ed25519Sha256 => HashAlgorithm::Sha256, |
1838 | + Algorithm::RsaSha1 => HashAlgorithm::Sha1, |
1839 | } |
1840 | } |
1841 | } |
1842 | |
1843 | - impl From<std::io::Error> for Error { |
1844 | - fn from(err: std::io::Error) -> Self { |
1845 | - Error::Io(err) |
1846 | + impl<'x> VerifySignature for Signature<'x> { |
1847 | + fn b(&self) -> &[u8] { |
1848 | + &self.b |
1849 | + } |
1850 | + |
1851 | + fn a(&self) -> Algorithm { |
1852 | + self.a |
1853 | } |
1854 | } |
1855 | diff --git a/src/dkim/parse.rs b/src/dkim/parse.rs |
1856 | index 3bb9355..f864bcd 100644 |
1857 | --- a/src/dkim/parse.rs |
1858 | +++ b/src/dkim/parse.rs |
1859 | @@ -1,18 +1,18 @@ |
1860 | - use std::slice::Iter; |
1861 | + use std::{borrow::Cow, slice::Iter}; |
1862 | |
1863 | use mail_parser::decoders::base64::base64_decode_stream; |
1864 | use rsa::RsaPublicKey; |
1865 | |
1866 | - use crate::common::parse::*; |
1867 | + use crate::{common::parse::*, Error}; |
1868 | |
1869 | use super::{ |
1870 | - Algorithm, Canonicalization, Error, Flag, HashAlgorithm, PublicKey, Record, Service, Signature, |
1871 | + Algorithm, Canonicalization, Flag, HashAlgorithm, PublicKey, Record, Service, Signature, |
1872 | Version, |
1873 | }; |
1874 | |
1875 | impl<'x> Signature<'x> { |
1876 | #[allow(clippy::while_let_on_iterator)] |
1877 | - pub fn parse(header: &'_ [u8]) -> super::Result<Self> { |
1878 | + pub fn parse(header: &'_ [u8]) -> crate::Result<Self> { |
1879 | let mut signature = Signature { |
1880 | v: 0, |
1881 | a: Algorithm::RsaSha256, |
1882 | @@ -85,15 +85,15 @@ pub(crate) trait SignatureParser: Sized { |
1883 | fn canonicalization( |
1884 | &mut self, |
1885 | default: Canonicalization, |
1886 | - ) -> super::Result<(Canonicalization, Canonicalization)>; |
1887 | - fn algorithm(&mut self) -> super::Result<Algorithm>; |
1888 | + ) -> crate::Result<(Canonicalization, Canonicalization)>; |
1889 | + fn algorithm(&mut self) -> crate::Result<Algorithm>; |
1890 | } |
1891 | |
1892 | impl SignatureParser for Iter<'_, u8> { |
1893 | fn canonicalization( |
1894 | &mut self, |
1895 | default: Canonicalization, |
1896 | - ) -> super::Result<(Canonicalization, Canonicalization)> { |
1897 | + ) -> crate::Result<(Canonicalization, Canonicalization)> { |
1898 | let mut cb = default; |
1899 | let mut ch = default; |
1900 | |
1901 | @@ -143,7 +143,7 @@ impl SignatureParser for Iter<'_, u8> { |
1902 | Ok((ch, cb)) |
1903 | } |
1904 | |
1905 | - fn algorithm(&mut self) -> super::Result<Algorithm> { |
1906 | + fn algorithm(&mut self) -> crate::Result<Algorithm> { |
1907 | match self.next_skip_whitespaces().unwrap_or(0) { |
1908 | b'r' | b'R' => { |
1909 | if self.match_bytes(b"sa-sha") { |
1910 | @@ -195,7 +195,7 @@ enum KeyType { |
1911 | |
1912 | impl Record { |
1913 | #[allow(clippy::while_let_on_iterator)] |
1914 | - pub fn parse(header: &[u8]) -> super::Result<Self> { |
1915 | + pub fn parse(header: &[u8]) -> crate::Result<Self> { |
1916 | let header_len = header.len(); |
1917 | let mut header = header.iter(); |
1918 | let mut record = Record { |
1919 | @@ -255,11 +255,11 @@ impl Record { |
1920 | KeyType::Rsa | KeyType::None => PublicKey::Rsa( |
1921 | <RsaPublicKey as rsa::pkcs8::DecodePublicKey>::from_public_key_der(&public_key) |
1922 | .or_else(|_| rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_der(&public_key)) |
1923 | - .map_err(Error::PKCS)?, |
1924 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
1925 | ), |
1926 | KeyType::Ed25519 => PublicKey::Ed25519( |
1927 | ed25519_dalek::PublicKey::from_bytes(&public_key) |
1928 | - .map_err(Error::Ed25519Signature)?, |
1929 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
1930 | ), |
1931 | } |
1932 | } |
1933 | @@ -272,6 +272,55 @@ impl Record { |
1934 | } |
1935 | } |
1936 | |
1937 | + pub trait TryIntoRecord<'x>: Sized { |
1938 | + fn try_into_record(self) -> crate::Result<Cow<'x, Record>>; |
1939 | + } |
1940 | + |
1941 | + impl<'x> TryIntoRecord<'x> for Record { |
1942 | + fn try_into_record(self) -> crate::Result<Cow<'x, Record>> { |
1943 | + Ok(Cow::Owned(self)) |
1944 | + } |
1945 | + } |
1946 | + |
1947 | + impl<'x> TryIntoRecord<'x> for &'x Record { |
1948 | + fn try_into_record(self) -> crate::Result<Cow<'x, Record>> { |
1949 | + Ok(Cow::Borrowed(self)) |
1950 | + } |
1951 | + } |
1952 | + |
1953 | + impl<'x> TryIntoRecord<'x> for String { |
1954 | + fn try_into_record(self) -> crate::Result<Cow<'x, Record>> { |
1955 | + Record::parse(self.as_bytes()).map(Cow::Owned) |
1956 | + } |
1957 | + } |
1958 | + |
1959 | + impl<'x> TryIntoRecord<'x> for &str { |
1960 | + fn try_into_record(self) -> crate::Result<Cow<'x, Record>> { |
1961 | + Record::parse(self.as_bytes()).map(Cow::Owned) |
1962 | + } |
1963 | + } |
1964 | + |
1965 | + impl<'x> TryIntoRecord<'x> for &[u8] { |
1966 | + fn try_into_record(self) -> crate::Result<Cow<'x, Record>> { |
1967 | + Record::parse(self).map(Cow::Owned) |
1968 | + } |
1969 | + } |
1970 | + |
1971 | + impl<'x> TryIntoRecord<'x> for Vec<u8> { |
1972 | + fn try_into_record(self) -> crate::Result<Cow<'x, Record>> { |
1973 | + Record::parse(&self).map(Cow::Owned) |
1974 | + } |
1975 | + } |
1976 | + |
1977 | + impl<'x, T: TryIntoRecord<'x> + Sized> TryIntoRecord<'x> for Option<T> { |
1978 | + fn try_into_record(self) -> crate::Result<Cow<'x, Record>> { |
1979 | + match self { |
1980 | + Some(v) => v.try_into_record(), |
1981 | + None => Err(Error::DNSFailure), |
1982 | + } |
1983 | + } |
1984 | + } |
1985 | + |
1986 | impl ItemParser for HashAlgorithm { |
1987 | fn parse(bytes: &[u8]) -> Option<Self> { |
1988 | if bytes.eq_ignore_ascii_case(b"sha256") { |
1989 | diff --git a/src/dkim/sign.rs b/src/dkim/sign.rs |
1990 | index 5da2dcc..06a337b 100644 |
1991 | --- a/src/dkim/sign.rs |
1992 | +++ b/src/dkim/sign.rs |
1993 | @@ -22,7 +22,9 @@ use rsa::{pkcs1::DecodeRsaPrivateKey, pkcs8::AssociatedOid, PaddingScheme, RsaPr |
1994 | use sha1::Sha1; |
1995 | use sha2::{Digest, Sha256}; |
1996 | |
1997 | - use super::{Algorithm, Canonicalization, DKIMSigner, Error, PrivateKey, Signature}; |
1998 | + use crate::Error; |
1999 | + |
2000 | + use super::{Algorithm, Canonicalization, DKIMSigner, PrivateKey, Signature}; |
2001 | |
2002 | impl<'x> DKIMSigner<'x> { |
2003 | /// Creates a new DKIM signer from an RsaPrivateKey. |
2004 | @@ -42,16 +44,20 @@ impl<'x> DKIMSigner<'x> { |
2005 | } |
2006 | |
2007 | /// Creates a new RSA private key from a PKCS1 PEM string. |
2008 | - pub fn rsa_pem(mut self, private_key_pem: &str) -> super::Result<Self> { |
2009 | - self.private_key = |
2010 | - PrivateKey::Rsa(RsaPrivateKey::from_pkcs1_pem(private_key_pem).map_err(Error::PKCS)?); |
2011 | + pub fn rsa_pem(mut self, private_key_pem: &str) -> crate::Result<Self> { |
2012 | + self.private_key = PrivateKey::Rsa( |
2013 | + RsaPrivateKey::from_pkcs1_pem(private_key_pem) |
2014 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
2015 | + ); |
2016 | Ok(self) |
2017 | } |
2018 | |
2019 | /// Creates a new RSA private key from a PKCS1 binary slice. |
2020 | - pub fn rsa(mut self, private_key_bytes: &[u8]) -> super::Result<Self> { |
2021 | - self.private_key = |
2022 | - PrivateKey::Rsa(RsaPrivateKey::from_pkcs1_der(private_key_bytes).map_err(Error::PKCS)?); |
2023 | + pub fn rsa(mut self, private_key_bytes: &[u8]) -> crate::Result<Self> { |
2024 | + self.private_key = PrivateKey::Rsa( |
2025 | + RsaPrivateKey::from_pkcs1_der(private_key_bytes) |
2026 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
2027 | + ); |
2028 | Ok(self) |
2029 | } |
2030 | |
2031 | @@ -60,12 +66,12 @@ impl<'x> DKIMSigner<'x> { |
2032 | mut self, |
2033 | public_key_bytes: &[u8], |
2034 | private_key_bytes: &[u8], |
2035 | - ) -> super::Result<Self> { |
2036 | + ) -> crate::Result<Self> { |
2037 | self.private_key = PrivateKey::Ed25519(ed25519_dalek::Keypair { |
2038 | public: ed25519_dalek::PublicKey::from_bytes(public_key_bytes) |
2039 | - .map_err(Error::Ed25519)?, |
2040 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
2041 | secret: ed25519_dalek::SecretKey::from_bytes(private_key_bytes) |
2042 | - .map_err(Error::Ed25519Signature)?, |
2043 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
2044 | }); |
2045 | self.a = Algorithm::Ed25519Sha256; |
2046 | Ok(self) |
2047 | @@ -139,7 +145,7 @@ impl<'x> DKIMSigner<'x> { |
2048 | |
2049 | /// Signs a message. |
2050 | #[inline(always)] |
2051 | - pub fn sign(&self, message: &[u8]) -> super::Result<Signature> { |
2052 | + pub fn sign(&self, message: &[u8]) -> crate::Result<Signature> { |
2053 | if !self.d.is_empty() && !self.s.is_empty() { |
2054 | let now = SystemTime::now() |
2055 | .duration_since(SystemTime::UNIX_EPOCH) |
2056 | @@ -156,7 +162,7 @@ impl<'x> DKIMSigner<'x> { |
2057 | } |
2058 | } |
2059 | |
2060 | - fn sign_<T>(&self, message: &[u8], now: u64) -> super::Result<Signature> |
2061 | + fn sign_<T>(&self, message: &[u8], now: u64) -> crate::Result<Signature> |
2062 | where |
2063 | T: Digest + AssociatedOid + std::io::Write, |
2064 | { |
2065 | @@ -199,7 +205,7 @@ impl<'x> DKIMSigner<'x> { |
2066 | PaddingScheme::new_pkcs1v15_sign::<T>(), |
2067 | &header_hasher.finalize(), |
2068 | ) |
2069 | - .map_err(Error::RSA)?, |
2070 | + .map_err(|err| Error::CryptoError(err.to_string()))?, |
2071 | PrivateKey::Ed25519(key_pair) => { |
2072 | key_pair.sign(&header_hasher.finalize()).to_bytes().to_vec() |
2073 | } |
2074 | @@ -347,8 +353,8 @@ mod test { |
2075 | use sha2::Sha256; |
2076 | |
2077 | use crate::{ |
2078 | - common::headers::HeaderIterator, |
2079 | - dkim::{verify::DKIMVerifier, Canonicalization, Record, Signature}, |
2080 | + common::{AuthResult, AuthenticatedMessage}, |
2081 | + dkim::{Canonicalization, Signature}, |
2082 | }; |
2083 | |
2084 | const RSA_PRIVATE_KEY: &str = r#"-----BEGIN RSA PRIVATE KEY----- |
2085 | @@ -556,25 +562,15 @@ GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= |
2086 | message.extend_from_slice(message_.as_bytes()); |
2087 | //println!("[{}]", String::from_utf8_lossy(&message)); |
2088 | |
2089 | - let mut headers_it = HeaderIterator::new(&message); |
2090 | - let headers = (&mut headers_it).collect::<Vec<_>>(); |
2091 | - let body = headers_it |
2092 | - .body_offset() |
2093 | - .and_then(|pos| message.get(pos..)) |
2094 | - .unwrap_or_default(); |
2095 | - let mut verifier = DKIMVerifier::new(&headers, body); |
2096 | - let mut num_signatures = 0; |
2097 | - |
2098 | - while let Some(signature) = verifier.next_signature() { |
2099 | - let signature = signature.unwrap(); |
2100 | - let record = Record::parse(public_key.as_bytes()).unwrap(); |
2101 | - match (verifier.verify(&signature, &record), &expect) { |
2102 | - (Ok(_), Ok(_)) | (Err(_), Err(_)) => (), |
2103 | - (result, expect) => panic!("Expected {:?} but got {:?}.", expect, result), |
2104 | - } |
2105 | - num_signatures += 1; |
2106 | + let mut verifier = AuthenticatedMessage::new(&message).unwrap(); |
2107 | + while verifier.next_entry().is_some() { |
2108 | + verifier.verify(public_key); |
2109 | } |
2110 | |
2111 | - assert_ne!(num_signatures, 0); |
2112 | + match (verifier.dkim_result, &expect) { |
2113 | + (AuthResult::Pass(_), Ok(_)) => (), |
2114 | + (AuthResult::PermFail(hdr), Err(err)) if &hdr.header == err => (), |
2115 | + (result, expect) => panic!("Expected {:?} but got {:?}.", expect, result), |
2116 | + } |
2117 | } |
2118 | } |
2119 | diff --git a/src/dkim/verify.rs b/src/dkim/verify.rs |
2120 | index ef3f9bd..19fb50f 100644 |
2121 | --- a/src/dkim/verify.rs |
2122 | +++ b/src/dkim/verify.rs |
2123 | @@ -1,129 +1,16 @@ |
2124 | - use std::{iter::Enumerate, slice::Iter, time::SystemTime}; |
2125 | - |
2126 | - use rsa::PaddingScheme; |
2127 | - use sha1::{Digest, Sha1}; |
2128 | - use sha2::Sha256; |
2129 | - |
2130 | - use super::{Algorithm, Canonicalization, Flag, HashAlgorithm, PublicKey, Record, Signature}; |
2131 | - |
2132 | - pub struct DKIMVerifier<'x> { |
2133 | - headers: &'x [(&'x [u8], &'x [u8])], |
2134 | - headers_iter: Enumerate<Iter<'x, (&'x [u8], &'x [u8])>>, |
2135 | - headers_pos: usize, |
2136 | - body: &'x [u8], |
2137 | - body_hashes: Vec<(Canonicalization, HashAlgorithm, u64, Vec<u8>)>, |
2138 | - } |
2139 | - |
2140 | - #[derive(Debug)] |
2141 | - pub struct Error<'x> { |
2142 | - pub(crate) error: super::Error, |
2143 | - pub(crate) header: &'x [u8], |
2144 | - } |
2145 | - |
2146 | - impl<'x> DKIMVerifier<'x> { |
2147 | - pub fn new(headers: &'x [(&'x [u8], &'x [u8])], body: &'x [u8]) -> Self { |
2148 | - DKIMVerifier { |
2149 | - headers, |
2150 | - headers_iter: headers.iter().enumerate(), |
2151 | - headers_pos: 0, |
2152 | - body, |
2153 | - body_hashes: Vec::new(), |
2154 | - } |
2155 | - } |
2156 | - |
2157 | - #[allow(clippy::while_let_on_iterator)] |
2158 | - pub fn verify(&mut self, signature: &Signature, record: &Record) -> Result<(), Error> { |
2159 | - let raw_signature = self.headers[self.headers_pos]; |
2160 | - |
2161 | - // Make sure the signature has not expired |
2162 | - if signature.x > 0 && signature.t > 0 { |
2163 | - let now = SystemTime::now() |
2164 | - .duration_since(SystemTime::UNIX_EPOCH) |
2165 | - .map(|d| d.as_secs()) |
2166 | - .unwrap_or(0); |
2167 | - if signature.x < signature.t || signature.x < now { |
2168 | - return Err(Error::new(super::Error::SignatureExpired, raw_signature.1)); |
2169 | - } |
2170 | - } |
2171 | - |
2172 | - // Enforce t=s flag |
2173 | - if !signature.i.is_empty() && record.has_flag(Flag::MatchDomain) { |
2174 | - let mut auid = signature.i.as_ref().iter(); |
2175 | - let mut domain = signature.d.as_ref().iter(); |
2176 | - while let Some(&ch) = auid.next() { |
2177 | - if ch == b'@' { |
2178 | - break; |
2179 | - } |
2180 | - } |
2181 | - while let Some(ch) = auid.next() { |
2182 | - if let Some(dch) = domain.next() { |
2183 | - if !ch.eq_ignore_ascii_case(dch) { |
2184 | - return Err(Error::new(super::Error::FailedAUIDMatch, raw_signature.1)); |
2185 | - } |
2186 | - } else { |
2187 | - break; |
2188 | - } |
2189 | - } |
2190 | - if domain.next().is_some() { |
2191 | - return Err(Error::new(super::Error::FailedAUIDMatch, raw_signature.1)); |
2192 | - } |
2193 | - } |
2194 | - |
2195 | - // Canonicalize the message body and calculate its hash |
2196 | - let bh = if let Some((_, _, _, bh)) = self.body_hashes.iter().find(|(c, h, l, _)| { |
2197 | - c == &signature.cb |
2198 | - && (matches!( |
2199 | - (signature.a, h), |
2200 | - ( |
2201 | - Algorithm::RsaSha256 | Algorithm::Ed25519Sha256, |
2202 | - HashAlgorithm::Sha256 |
2203 | - ) | (Algorithm::RsaSha1, HashAlgorithm::Sha1) |
2204 | - ) && l == &signature.l) |
2205 | - }) { |
2206 | - bh |
2207 | - } else { |
2208 | - let body = if signature.l == 0 || self.body.is_empty() { |
2209 | - self.body |
2210 | - } else { |
2211 | - &self.body[..std::cmp::min(signature.l as usize, self.body.len())] |
2212 | - }; |
2213 | - let (bh, h) = match signature.a { |
2214 | - Algorithm::RsaSha256 | Algorithm::Ed25519Sha256 => { |
2215 | - let mut hasher = Sha256::new(); |
2216 | - signature |
2217 | - .cb |
2218 | - .canonicalize_body(body, &mut hasher) |
2219 | - .map_err(|err| Error::new(err.into(), raw_signature.1))?; |
2220 | - (hasher.finalize().to_vec(), HashAlgorithm::Sha256) |
2221 | - } |
2222 | - Algorithm::RsaSha1 => { |
2223 | - let mut hasher = Sha1::new(); |
2224 | - signature |
2225 | - .cb |
2226 | - .canonicalize_body(body, &mut hasher) |
2227 | - .map_err(|err| Error::new(err.into(), raw_signature.1))?; |
2228 | - (hasher.finalize().to_vec(), HashAlgorithm::Sha1) |
2229 | - } |
2230 | - }; |
2231 | - self.body_hashes.push((signature.cb, h, signature.l, bh)); |
2232 | - &self.body_hashes.last().unwrap().3 |
2233 | - }; |
2234 | - |
2235 | - // Check that the body hash matches |
2236 | - if bh != &signature.bh { |
2237 | - return Err(Error::new( |
2238 | - super::Error::FailedBodyHashMatch, |
2239 | - raw_signature.1, |
2240 | - )); |
2241 | - } |
2242 | - |
2243 | - // Create header iterator |
2244 | + use crate::common::AuthenticatedMessage; |
2245 | + |
2246 | + impl<'x> AuthenticatedMessage<'x> { |
2247 | + pub fn signed_headers<'z: 'x>( |
2248 | + &'z self, |
2249 | + headers: &'x [Vec<u8>], |
2250 | + dkim_hdr_name: &'x [u8], |
2251 | + dkim_hdr_value: &'x [u8], |
2252 | + ) -> impl Iterator<Item = (&'x [u8], &'x [u8])> { |
2253 | let mut last_header_pos: Vec<(&[u8], usize)> = Vec::new(); |
2254 | - let unsigned_dkim = strip_signature(raw_signature.1); |
2255 | - let headers = signature |
2256 | - .h |
2257 | + headers |
2258 | .iter() |
2259 | - .filter_map(|h| { |
2260 | + .filter_map(move |h| { |
2261 | let header_pos = if let Some((_, header_pos)) = last_header_pos |
2262 | .iter_mut() |
2263 | .find(|(lh, _)| lh.eq_ignore_ascii_case(h)) |
2264 | @@ -148,175 +35,58 @@ impl<'x> DKIMVerifier<'x> { |
2265 | None |
2266 | } |
2267 | }) |
2268 | - .chain([(raw_signature.0, unsigned_dkim.as_ref())]); |
2269 | - |
2270 | - // Canonicalize and hash headers |
2271 | - let hh = match signature.a { |
2272 | - Algorithm::RsaSha256 | Algorithm::Ed25519Sha256 => { |
2273 | - let mut hasher = Sha256::new(); |
2274 | - signature |
2275 | - .ch |
2276 | - .canonicalize_headers(headers, &mut hasher) |
2277 | - .map_err(|err| Error::new(err.into(), raw_signature.1))?; |
2278 | - hasher.finalize().to_vec() |
2279 | - } |
2280 | - Algorithm::RsaSha1 => { |
2281 | - let mut hasher = Sha1::new(); |
2282 | - signature |
2283 | - .ch |
2284 | - .canonicalize_headers(headers, &mut hasher) |
2285 | - .map_err(|err| Error::new(err.into(), raw_signature.1))?; |
2286 | - hasher.finalize().to_vec() |
2287 | - } |
2288 | - }; |
2289 | - |
2290 | - // Verify signature |
2291 | - match (&signature.a, &record.p) { |
2292 | - (Algorithm::RsaSha256, PublicKey::Rsa(public_key)) => rsa::PublicKey::verify( |
2293 | - public_key, |
2294 | - PaddingScheme::new_pkcs1v15_sign::<Sha256>(), |
2295 | - &hh, |
2296 | - &signature.b, |
2297 | - ) |
2298 | - .map_err(|_| Error::new(super::Error::FailedVerification, raw_signature.1)), |
2299 | - |
2300 | - (Algorithm::RsaSha1, PublicKey::Rsa(public_key)) => rsa::PublicKey::verify( |
2301 | - public_key, |
2302 | - PaddingScheme::new_pkcs1v15_sign::<Sha1>(), |
2303 | - &hh, |
2304 | - &signature.b, |
2305 | - ) |
2306 | - .map_err(|_| Error::new(super::Error::FailedVerification, raw_signature.1)), |
2307 | - |
2308 | - (Algorithm::Ed25519Sha256, PublicKey::Ed25519(public_key)) => public_key |
2309 | - .verify_strict( |
2310 | - &hh, |
2311 | - &ed25519_dalek::Signature::from_bytes(&signature.b).map_err(|err| { |
2312 | - Error::new(super::Error::Ed25519Signature(err), raw_signature.1) |
2313 | - })?, |
2314 | - ) |
2315 | - .map_err(|_| Error::new(super::Error::FailedVerification, raw_signature.1)), |
2316 | - |
2317 | - (_, PublicKey::Revoked) => { |
2318 | - Err(Error::new(super::Error::RevokedPublicKey, raw_signature.1)) |
2319 | - } |
2320 | - |
2321 | - (_, _) => Err(Error::new( |
2322 | - super::Error::IncompatibleAlgorithms, |
2323 | - raw_signature.1, |
2324 | - )), |
2325 | - } |
2326 | + .chain([(dkim_hdr_name, dkim_hdr_value)]) |
2327 | } |
2328 | + } |
2329 | |
2330 | - pub fn next_signature<'z>(&mut self) -> Option<Result<Signature<'z>, Error<'x>>> { |
2331 | - for (pos, (name, value)) in &mut self.headers_iter { |
2332 | - if name.eq_ignore_ascii_case(b"dkim-signature") { |
2333 | - self.headers_pos = pos; |
2334 | - return Signature::parse(value) |
2335 | - .map_err(|error| Error { |
2336 | - error, |
2337 | - header: value, |
2338 | - }) |
2339 | - .into(); |
2340 | - } |
2341 | - } |
2342 | - |
2343 | - None |
2344 | - } |
2345 | + pub(crate) trait Verifier: Sized { |
2346 | + fn strip_signature(&self) -> Vec<u8>; |
2347 | } |
2348 | |
2349 | - fn strip_signature(bytes: &[u8]) -> Vec<u8> { |
2350 | - let mut unsigned_dkim = Vec::with_capacity(bytes.len()); |
2351 | - let mut iter = bytes.iter().enumerate(); |
2352 | - let mut last_ch = b';'; |
2353 | - while let Some((pos, &ch)) = iter.next() { |
2354 | - match ch { |
2355 | - b'=' if last_ch == b'b' => { |
2356 | - unsigned_dkim.push(ch); |
2357 | - #[allow(clippy::while_let_on_iterator)] |
2358 | - while let Some((_, &ch)) = iter.next() { |
2359 | - if ch == b';' { |
2360 | - unsigned_dkim.push(b';'); |
2361 | - break; |
2362 | + impl Verifier for &[u8] { |
2363 | + fn strip_signature(&self) -> Vec<u8> { |
2364 | + let mut unsigned_dkim = Vec::with_capacity(self.len()); |
2365 | + let mut iter = self.iter().enumerate(); |
2366 | + let mut last_ch = b';'; |
2367 | + while let Some((pos, &ch)) = iter.next() { |
2368 | + match ch { |
2369 | + b'=' if last_ch == b'b' => { |
2370 | + unsigned_dkim.push(ch); |
2371 | + #[allow(clippy::while_let_on_iterator)] |
2372 | + while let Some((_, &ch)) = iter.next() { |
2373 | + if ch == b';' { |
2374 | + unsigned_dkim.push(b';'); |
2375 | + break; |
2376 | + } |
2377 | } |
2378 | - } |
2379 | - last_ch = 0; |
2380 | - } |
2381 | - b'b' | b'B' if last_ch == b';' => { |
2382 | - last_ch = b'b'; |
2383 | - unsigned_dkim.push(ch); |
2384 | - } |
2385 | - b';' => { |
2386 | - last_ch = b';'; |
2387 | - unsigned_dkim.push(ch); |
2388 | - } |
2389 | - b'\r' if pos == bytes.len() - 2 => (), |
2390 | - b'\n' if pos == bytes.len() - 1 => (), |
2391 | - _ => { |
2392 | - unsigned_dkim.push(ch); |
2393 | - if !ch.is_ascii_whitespace() { |
2394 | last_ch = 0; |
2395 | } |
2396 | + b'b' | b'B' if last_ch == b';' => { |
2397 | + last_ch = b'b'; |
2398 | + unsigned_dkim.push(ch); |
2399 | + } |
2400 | + b';' => { |
2401 | + last_ch = b';'; |
2402 | + unsigned_dkim.push(ch); |
2403 | + } |
2404 | + b'\r' if pos == self.len() - 2 => (), |
2405 | + b'\n' if pos == self.len() - 1 => (), |
2406 | + _ => { |
2407 | + unsigned_dkim.push(ch); |
2408 | + if !ch.is_ascii_whitespace() { |
2409 | + last_ch = 0; |
2410 | + } |
2411 | + } |
2412 | } |
2413 | } |
2414 | - } |
2415 | - unsigned_dkim |
2416 | - } |
2417 | - |
2418 | - impl<'x> Error<'x> { |
2419 | - pub fn new(error: super::Error, header: &'x [u8]) -> Self { |
2420 | - Error { error, header } |
2421 | + unsigned_dkim |
2422 | } |
2423 | } |
2424 | |
2425 | #[cfg(test)] |
2426 | mod test { |
2427 | - use std::{collections::HashMap, fs, path::PathBuf}; |
2428 | - |
2429 | - use crate::{common::headers::HeaderIterator, dkim::Record}; |
2430 | - |
2431 | - use super::{strip_signature, DKIMVerifier}; |
2432 | - |
2433 | - #[test] |
2434 | - fn dkim_verify() { |
2435 | - let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); |
2436 | - test_dir.push("resources"); |
2437 | - test_dir.push("dkim"); |
2438 | - |
2439 | - for file_name in fs::read_dir(&test_dir).unwrap() { |
2440 | - let file_name = file_name.unwrap().path(); |
2441 | - /*if !file_name.to_str().unwrap().contains("002") { |
2442 | - continue; |
2443 | - }*/ |
2444 | - println!("file {}", file_name.to_str().unwrap()); |
2445 | - |
2446 | - let test = String::from_utf8(fs::read(&file_name).unwrap()).unwrap(); |
2447 | - let (dns_records, message) = test.split_once("\n\n").unwrap(); |
2448 | - let dns_records = dns_records |
2449 | - .split('\n') |
2450 | - .filter_map(|r| r.split_once(' ').map(|(a, b)| (a.as_bytes(), b.as_bytes()))) |
2451 | - .collect::<HashMap<_, _>>(); |
2452 | - let message = message.replace('\n', "\r\n"); |
2453 | - |
2454 | - let mut headers_it = HeaderIterator::new(message.as_bytes()); |
2455 | - let headers = (&mut headers_it).collect::<Vec<_>>(); |
2456 | - let body = headers_it |
2457 | - .body_offset() |
2458 | - .and_then(|pos| message.as_bytes().get(pos..)) |
2459 | - .unwrap_or_default(); |
2460 | - let mut verifier = DKIMVerifier::new(&headers, body); |
2461 | - let mut num_signatures = 0; |
2462 | |
2463 | - while let Some(signature) = verifier.next_signature() { |
2464 | - let signature = signature.unwrap(); |
2465 | - let record = Record::parse(dns_records.get(signature.s.as_ref()).unwrap()).unwrap(); |
2466 | - verifier.verify(&signature, &record).unwrap(); |
2467 | - num_signatures += 1; |
2468 | - } |
2469 | - |
2470 | - assert_ne!(num_signatures, 0); |
2471 | - } |
2472 | - } |
2473 | + use crate::dkim::verify::Verifier; |
2474 | |
2475 | #[test] |
2476 | fn dkim_strip_signature() { |
2477 | @@ -327,7 +97,7 @@ mod test { |
2478 | ("B\r\n=abc;v=1\r\n", "B\r\n=;v=1"), |
2479 | ] { |
2480 | assert_eq!( |
2481 | - String::from_utf8(strip_signature(value.as_bytes())).unwrap(), |
2482 | + String::from_utf8(value.as_bytes().strip_signature()).unwrap(), |
2483 | stripped_value |
2484 | ); |
2485 | } |
2486 | diff --git a/src/dmarc/mod.rs b/src/dmarc/mod.rs |
2487 | index 299dc6f..cf0d610 100644 |
2488 | --- a/src/dmarc/mod.rs |
2489 | +++ b/src/dmarc/mod.rs |
2490 | @@ -56,6 +56,7 @@ impl From<Format> for u64 { |
2491 | } |
2492 | |
2493 | impl URI { |
2494 | + #[cfg(test)] |
2495 | pub fn new(uri: impl Into<String>, max_size: usize) -> Self { |
2496 | URI { |
2497 | uri: uri.into().into_bytes(), |
2498 | @@ -63,12 +64,3 @@ impl URI { |
2499 | } |
2500 | } |
2501 | } |
2502 | - |
2503 | - #[derive(Debug)] |
2504 | - pub enum Error { |
2505 | - InvalidVersion, |
2506 | - InvalidRecord, |
2507 | - ParseFailed, |
2508 | - } |
2509 | - |
2510 | - pub type Result<T> = std::result::Result<T, Error>; |
2511 | diff --git a/src/dmarc/parse.rs b/src/dmarc/parse.rs |
2512 | index 53a94c2..0bb0d23 100644 |
2513 | --- a/src/dmarc/parse.rs |
2514 | +++ b/src/dmarc/parse.rs |
2515 | @@ -2,12 +2,15 @@ use std::slice::Iter; |
2516 | |
2517 | use mail_parser::decoders::quoted_printable::quoted_printable_decode_char; |
2518 | |
2519 | - use crate::common::parse::{ItemParser, TagParser, V}; |
2520 | + use crate::{ |
2521 | + common::parse::{ItemParser, TagParser, V}, |
2522 | + Error, |
2523 | + }; |
2524 | |
2525 | - use super::{Alignment, Error, Format, Policy, Report, DMARC, URI}; |
2526 | + use super::{Alignment, Format, Policy, Report, DMARC, URI}; |
2527 | |
2528 | impl DMARC { |
2529 | - pub fn parse(bytes: &[u8]) -> super::Result<Self> { |
2530 | + pub fn parse(bytes: &[u8]) -> crate::Result<Self> { |
2531 | let mut record = bytes.iter(); |
2532 | if record.key().unwrap_or(0) != V { |
2533 | return Err(Error::InvalidRecord); |
2534 | @@ -47,14 +50,13 @@ impl DMARC { |
2535 | dmarc.p = record.policy()?; |
2536 | } |
2537 | PCT => { |
2538 | - dmarc.pct = |
2539 | - std::cmp::min(100, record.number().ok_or(Error::ParseFailed)?) as u8; |
2540 | + dmarc.pct = std::cmp::min(100, record.number().ok_or(Error::ParseError)?) as u8; |
2541 | } |
2542 | RF => { |
2543 | dmarc.rf = record.flags::<Format>() as u8; |
2544 | } |
2545 | RI => { |
2546 | - dmarc.ri = record.number().ok_or(Error::ParseFailed)? as u32; |
2547 | + dmarc.ri = record.number().ok_or(Error::ParseError)? as u32; |
2548 | } |
2549 | RUA => { |
2550 | dmarc.rua = record.uris()?; |
2551 | @@ -83,57 +85,57 @@ impl DMARC { |
2552 | } |
2553 | |
2554 | pub(crate) trait DMARCParser: Sized { |
2555 | - fn alignment(&mut self) -> super::Result<Alignment>; |
2556 | - fn report(&mut self) -> super::Result<Report>; |
2557 | - fn policy(&mut self) -> super::Result<Policy>; |
2558 | - fn uris(&mut self) -> super::Result<Vec<URI>>; |
2559 | + fn alignment(&mut self) -> crate::Result<Alignment>; |
2560 | + fn report(&mut self) -> crate::Result<Report>; |
2561 | + fn policy(&mut self) -> crate::Result<Policy>; |
2562 | + fn uris(&mut self) -> crate::Result<Vec<URI>>; |
2563 | } |
2564 | |
2565 | impl DMARCParser for Iter<'_, u8> { |
2566 | - fn alignment(&mut self) -> super::Result<Alignment> { |
2567 | + fn alignment(&mut self) -> crate::Result<Alignment> { |
2568 | let a = match self.next_skip_whitespaces().unwrap_or(0) { |
2569 | b'r' | b'R' => Alignment::Relaxed, |
2570 | b's' | b'S' => Alignment::Strict, |
2571 | - _ => return Err(Error::ParseFailed), |
2572 | + _ => return Err(Error::ParseError), |
2573 | }; |
2574 | if self.seek_tag_end() { |
2575 | Ok(a) |
2576 | } else { |
2577 | - Err(Error::ParseFailed) |
2578 | + Err(Error::ParseError) |
2579 | } |
2580 | } |
2581 | |
2582 | - fn report(&mut self) -> super::Result<Report> { |
2583 | + fn report(&mut self) -> crate::Result<Report> { |
2584 | let r = match self.next_skip_whitespaces().unwrap_or(0) { |
2585 | b'0' => Report::All, |
2586 | b'1' => Report::Any, |
2587 | b'd' | b'D' => Report::Dkim, |
2588 | b's' | b'S' => Report::Spf, |
2589 | - _ => return Err(Error::ParseFailed), |
2590 | + _ => return Err(Error::ParseError), |
2591 | }; |
2592 | if self.seek_tag_end() { |
2593 | Ok(r) |
2594 | } else { |
2595 | - Err(Error::ParseFailed) |
2596 | + Err(Error::ParseError) |
2597 | } |
2598 | } |
2599 | |
2600 | - fn policy(&mut self) -> super::Result<Policy> { |
2601 | + fn policy(&mut self) -> crate::Result<Policy> { |
2602 | let p = match self.next_skip_whitespaces().unwrap_or(0) { |
2603 | b'n' | b'N' if self.match_bytes(b"one") => Policy::None, |
2604 | b'q' | b'Q' if self.match_bytes(b"uarantine") => Policy::Quarantine, |
2605 | b'r' | b'R' if self.match_bytes(b"eject") => Policy::Reject, |
2606 | - _ => return Err(Error::ParseFailed), |
2607 | + _ => return Err(Error::ParseError), |
2608 | }; |
2609 | if self.seek_tag_end() { |
2610 | Ok(p) |
2611 | } else { |
2612 | - Err(Error::ParseFailed) |
2613 | + Err(Error::ParseError) |
2614 | } |
2615 | } |
2616 | |
2617 | #[allow(clippy::while_let_on_iterator)] |
2618 | - fn uris(&mut self) -> super::Result<Vec<URI>> { |
2619 | + fn uris(&mut self) -> crate::Result<Vec<URI>> { |
2620 | let mut uris = Vec::new(); |
2621 | let mut uri = Vec::with_capacity(16); |
2622 | let mut size: usize = 0; |
2623 | @@ -156,7 +158,7 @@ impl DMARCParser for Iter<'_, u8> { |
2624 | } else if ch == b';' { |
2625 | break 'outer; |
2626 | } else if !ch.is_ascii_whitespace() { |
2627 | - return Err(Error::ParseFailed); |
2628 | + return Err(Error::ParseError); |
2629 | } |
2630 | } |
2631 | } |
2632 | @@ -203,7 +205,7 @@ impl DMARCParser for Iter<'_, u8> { |
2633 | } |
2634 | _ => { |
2635 | if !ch.is_ascii_whitespace() { |
2636 | - return Err(Error::ParseFailed); |
2637 | + return Err(Error::ParseError); |
2638 | } |
2639 | } |
2640 | } |
2641 | diff --git a/src/lib.rs b/src/lib.rs |
2642 | index d7bd8da..61f73a9 100644 |
2643 | --- a/src/lib.rs |
2644 | +++ b/src/lib.rs |
2645 | @@ -9,12 +9,113 @@ |
2646 | * except according to those terms. |
2647 | */ |
2648 | |
2649 | + use std::fmt::Display; |
2650 | + |
2651 | pub mod arc; |
2652 | pub mod common; |
2653 | pub mod dkim; |
2654 | pub mod dmarc; |
2655 | pub mod spf; |
2656 | |
2657 | + #[derive(Debug, Clone, PartialEq, Eq)] |
2658 | + pub enum Error { |
2659 | + ParseError, |
2660 | + MissingParameters, |
2661 | + NoHeadersFound, |
2662 | + CryptoError(String), |
2663 | + Io(String), |
2664 | + Base64, |
2665 | + UnsupportedVersion, |
2666 | + UnsupportedAlgorithm, |
2667 | + UnsupportedCanonicalization, |
2668 | + UnsupportedRecordVersion, |
2669 | + UnsupportedKeyType, |
2670 | + FailedBodyHashMatch, |
2671 | + RevokedPublicKey, |
2672 | + IncompatibleAlgorithms, |
2673 | + FailedVerification, |
2674 | + SignatureExpired, |
2675 | + FailedAUIDMatch, |
2676 | + DNSFailure, |
2677 | + |
2678 | + ARCInvalidInstance, |
2679 | + ARCInvalidCV, |
2680 | + ARCHasHeaderTag, |
2681 | + ARCBrokenChain, |
2682 | + |
2683 | + InvalidVersion, |
2684 | + InvalidRecord, |
2685 | + |
2686 | + InvalidIp4, |
2687 | + InvalidIp6, |
2688 | + InvalidMacro, |
2689 | + } |
2690 | + |
2691 | + pub type Result<T> = std::result::Result<T, Error>; |
2692 | + |
2693 | + impl Display for Error { |
2694 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
2695 | + match self { |
2696 | + Error::ParseError => write!(f, "Parse error"), |
2697 | + Error::MissingParameters => write!(f, "Missing parameters"), |
2698 | + Error::NoHeadersFound => write!(f, "No headers found"), |
2699 | + Error::CryptoError(err) => write!(f, "Cryptography layer error: {}", err), |
2700 | + Error::Io(e) => write!(f, "I/O error: {}", e), |
2701 | + Error::Base64 => write!(f, "Base64 encode or decode error."), |
2702 | + Error::UnsupportedVersion => write!(f, "Unsupported version in DKIM Signature."), |
2703 | + Error::UnsupportedAlgorithm => write!(f, "Unsupported algorithm in DKIM Signature."), |
2704 | + Error::UnsupportedCanonicalization => { |
2705 | + write!(f, "Unsupported canonicalization method in DKIM Signature.") |
2706 | + } |
2707 | + Error::UnsupportedRecordVersion => { |
2708 | + write!(f, "Unsupported version in DKIM DNS record.") |
2709 | + } |
2710 | + Error::UnsupportedKeyType => { |
2711 | + write!(f, "Unsupported key type in DKIM DNS record.") |
2712 | + } |
2713 | + Error::FailedBodyHashMatch => { |
2714 | + write!(f, "Calculated body hash does not match signature hash.") |
2715 | + } |
2716 | + Error::RevokedPublicKey => write!(f, "Public key for this signature has been revoked."), |
2717 | + Error::IncompatibleAlgorithms => write!( |
2718 | + f, |
2719 | + "Incompatible algorithms used in signature and DKIM DNS record." |
2720 | + ), |
2721 | + Error::FailedVerification => write!(f, "Signature verification failed."), |
2722 | + Error::SignatureExpired => write!(f, "Signature expired."), |
2723 | + Error::FailedAUIDMatch => write!(f, "AUID does not match domain name."), |
2724 | + Error::ARCInvalidInstance => write!(f, "Invalid 'i=' value found in ARC header."), |
2725 | + Error::ARCInvalidCV => write!(f, "Invalid 'cv=' value found in ARC header."), |
2726 | + Error::ARCHasHeaderTag => write!(f, "Invalid 'h=' tag present in ARC-Seal."), |
2727 | + Error::ARCBrokenChain => write!(f, "Broken or missing ARC chain."), |
2728 | + Error::InvalidVersion => write!(f, "Invalid version."), |
2729 | + Error::InvalidRecord => write!(f, "Invalid record."), |
2730 | + Error::InvalidIp4 => write!(f, "Invalid IPv4."), |
2731 | + Error::InvalidIp6 => write!(f, "Invalid IPv6."), |
2732 | + Error::InvalidMacro => write!(f, "Invalid SPF macro."), |
2733 | + Error::DNSFailure => write!(f, "DNS failure."), |
2734 | + } |
2735 | + } |
2736 | + } |
2737 | + |
2738 | + impl From<std::io::Error> for Error { |
2739 | + fn from(err: std::io::Error) -> Self { |
2740 | + Error::Io(err.to_string()) |
2741 | + } |
2742 | + } |
2743 | + |
2744 | + impl From<rsa::errors::Error> for Error { |
2745 | + fn from(err: rsa::errors::Error) -> Self { |
2746 | + Error::CryptoError(err.to_string()) |
2747 | + } |
2748 | + } |
2749 | + |
2750 | + impl From<ed25519_dalek::ed25519::Error> for Error { |
2751 | + fn from(err: ed25519_dalek::ed25519::Error) -> Self { |
2752 | + Error::CryptoError(err.to_string()) |
2753 | + } |
2754 | + } |
2755 | + |
2756 | pub fn add(left: usize, right: usize) -> usize { |
2757 | left + right |
2758 | } |
2759 | diff --git a/src/spf/mod.rs b/src/spf/mod.rs |
2760 | index 4658aa9..3e56ad6 100644 |
2761 | --- a/src/spf/mod.rs |
2762 | +++ b/src/spf/mod.rs |
2763 | @@ -138,18 +138,6 @@ pub(crate) enum Version { |
2764 | Spf1, |
2765 | } |
2766 | |
2767 | - #[derive(Debug)] |
2768 | - pub enum Error { |
2769 | - InvalidVersion, |
2770 | - InvalidRecord, |
2771 | - InvalidIp4, |
2772 | - InvalidIp6, |
2773 | - InvalidMacro, |
2774 | - ParseFailed, |
2775 | - } |
2776 | - |
2777 | - pub type Result<T> = std::result::Result<T, Error>; |
2778 | - |
2779 | impl Directive { |
2780 | pub fn new(qualifier: Qualifier, mechanism: Mechanism) -> Self { |
2781 | Directive { |
2782 | diff --git a/src/spf/parse.rs b/src/spf/parse.rs |
2783 | index fe89a80..144e433 100644 |
2784 | --- a/src/spf/parse.rs |
2785 | +++ b/src/spf/parse.rs |
2786 | @@ -3,12 +3,15 @@ use std::{ |
2787 | slice::Iter, |
2788 | }; |
2789 | |
2790 | - use crate::common::parse::{TagParser, V}; |
2791 | + use crate::{ |
2792 | + common::parse::{TagParser, V}, |
2793 | + Error, |
2794 | + }; |
2795 | |
2796 | - use super::{Directive, Error, Macro, Mechanism, Modifier, Qualifier, Variable, SPF}; |
2797 | + use super::{Directive, Macro, Mechanism, Modifier, Qualifier, Variable, SPF}; |
2798 | |
2799 | impl SPF { |
2800 | - pub fn parse(bytes: &[u8]) -> super::Result<SPF> { |
2801 | + pub fn parse(bytes: &[u8]) -> crate::Result<SPF> { |
2802 | let mut record = bytes.iter(); |
2803 | if !matches!(record.key(), Some(k) if k == V) { |
2804 | return Err(Error::InvalidRecord); |
2805 | @@ -41,7 +44,7 @@ impl SPF { |
2806 | ip4_cidr_length = l1; |
2807 | ip6_cidr_length = l2; |
2808 | } else if stop_char != b' ' { |
2809 | - return Err(Error::ParseFailed); |
2810 | + return Err(Error::ParseError); |
2811 | } |
2812 | } |
2813 | b'/' => { |
2814 | @@ -49,7 +52,7 @@ impl SPF { |
2815 | ip4_cidr_length = l1; |
2816 | ip6_cidr_length = l2; |
2817 | } |
2818 | - _ => return Err(Error::ParseFailed), |
2819 | + _ => return Err(Error::ParseError), |
2820 | } |
2821 | |
2822 | spf.directives.push(Directive::new( |
2823 | @@ -74,12 +77,12 @@ impl SPF { |
2824 | spf.directives |
2825 | .push(Directive::new(qualifier, Mechanism::All)) |
2826 | } else { |
2827 | - return Err(Error::ParseFailed); |
2828 | + return Err(Error::ParseError); |
2829 | } |
2830 | } |
2831 | INCLUDE | EXISTS => { |
2832 | if stop_char != b':' { |
2833 | - return Err(Error::ParseFailed); |
2834 | + return Err(Error::ParseError); |
2835 | } |
2836 | let (macro_string, stop_char) = record.macro_string(false)?; |
2837 | if stop_char == b' ' { |
2838 | @@ -92,19 +95,19 @@ impl SPF { |
2839 | }, |
2840 | )); |
2841 | } else { |
2842 | - return Err(Error::ParseFailed); |
2843 | + return Err(Error::ParseError); |
2844 | } |
2845 | } |
2846 | IP4 => { |
2847 | if stop_char != b':' { |
2848 | - return Err(Error::ParseFailed); |
2849 | + return Err(Error::ParseError); |
2850 | } |
2851 | let mut cidr_length = 32; |
2852 | let (addr, stop_char) = record.ip4()?; |
2853 | if stop_char == b'/' { |
2854 | cidr_length = std::cmp::min(cidr_length, record.cidr_length()?); |
2855 | } else if stop_char != b' ' { |
2856 | - return Err(Error::ParseFailed); |
2857 | + return Err(Error::ParseError); |
2858 | } |
2859 | spf.directives.push(Directive::new( |
2860 | qualifier, |
2861 | @@ -113,14 +116,14 @@ impl SPF { |
2862 | } |
2863 | IP6 => { |
2864 | if stop_char != b':' { |
2865 | - return Err(Error::ParseFailed); |
2866 | + return Err(Error::ParseError); |
2867 | } |
2868 | let mut cidr_length = 128; |
2869 | let (addr, stop_char) = record.ip6()?; |
2870 | if stop_char == b'/' { |
2871 | cidr_length = std::cmp::min(cidr_length, record.cidr_length()?); |
2872 | } else if stop_char != b' ' { |
2873 | - return Err(Error::ParseFailed); |
2874 | + return Err(Error::ParseError); |
2875 | } |
2876 | spf.directives.push(Directive::new( |
2877 | qualifier, |
2878 | @@ -139,16 +142,16 @@ impl SPF { |
2879 | spf.directives |
2880 | .push(Directive::new(qualifier, Mechanism::Ptr { macro_string })); |
2881 | } else { |
2882 | - return Err(Error::ParseFailed); |
2883 | + return Err(Error::ParseError); |
2884 | } |
2885 | } |
2886 | EXP | REDIRECT => { |
2887 | if stop_char != b'=' { |
2888 | - return Err(Error::ParseFailed); |
2889 | + return Err(Error::ParseError); |
2890 | } |
2891 | let (macro_string, stop_char) = record.macro_string(false)?; |
2892 | if stop_char != b' ' { |
2893 | - return Err(Error::ParseFailed); |
2894 | + return Err(Error::ParseError); |
2895 | } |
2896 | spf.modifiers.push(if term == REDIRECT { |
2897 | Modifier::Redirect(macro_string) |
2898 | @@ -159,7 +162,7 @@ impl SPF { |
2899 | _ => { |
2900 | let (_, stop_char) = record.macro_string(false)?; |
2901 | if stop_char != b' ' { |
2902 | - return Err(Error::ParseFailed); |
2903 | + return Err(Error::ParseError); |
2904 | } |
2905 | } |
2906 | } |
2907 | @@ -200,11 +203,11 @@ const REDIRECT: u64 = (b't' as u64) << 56 |
2908 | |
2909 | pub(crate) trait SPFParser: Sized { |
2910 | fn next_term(&mut self) -> Option<(u64, Qualifier, u8)>; |
2911 | - fn macro_string(&mut self, is_exp: bool) -> super::Result<(Macro, u8)>; |
2912 | - fn ip4(&mut self) -> super::Result<(Ipv4Addr, u8)>; |
2913 | - fn ip6(&mut self) -> super::Result<(Ipv6Addr, u8)>; |
2914 | - fn cidr_length(&mut self) -> super::Result<u8>; |
2915 | - fn dual_cidr_length(&mut self) -> super::Result<(u8, u8)>; |
2916 | + fn macro_string(&mut self, is_exp: bool) -> crate::Result<(Macro, u8)>; |
2917 | + fn ip4(&mut self) -> crate::Result<(Ipv4Addr, u8)>; |
2918 | + fn ip6(&mut self) -> crate::Result<(Ipv6Addr, u8)>; |
2919 | + fn cidr_length(&mut self) -> crate::Result<u8>; |
2920 | + fn dual_cidr_length(&mut self) -> crate::Result<(u8, u8)>; |
2921 | } |
2922 | |
2923 | impl SPFParser for Iter<'_, u8> { |
2924 | @@ -262,7 +265,7 @@ impl SPFParser for Iter<'_, u8> { |
2925 | } |
2926 | |
2927 | #[allow(clippy::while_let_on_iterator)] |
2928 | - fn macro_string(&mut self, is_exp: bool) -> super::Result<(Macro, u8)> { |
2929 | + fn macro_string(&mut self, is_exp: bool) -> crate::Result<(Macro, u8)> { |
2930 | let mut stop_char = b' '; |
2931 | let mut last_is_pct = false; |
2932 | let mut literal = Vec::with_capacity(16); |
2933 | @@ -363,12 +366,12 @@ impl SPFParser for Iter<'_, u8> { |
2934 | |
2935 | match macro_string.len() { |
2936 | 1 => Ok((macro_string.pop().unwrap(), stop_char)), |
2937 | - 0 => Err(Error::ParseFailed), |
2938 | + 0 => Err(Error::ParseError), |
2939 | _ => Ok((Macro::List(macro_string), stop_char)), |
2940 | } |
2941 | } |
2942 | |
2943 | - fn ip4(&mut self) -> super::Result<(Ipv4Addr, u8)> { |
2944 | + fn ip4(&mut self) -> crate::Result<(Ipv4Addr, u8)> { |
2945 | let mut stop_char = b' '; |
2946 | let mut pos = 0; |
2947 | let mut ip = [0u8; 4]; |
2948 | @@ -395,7 +398,7 @@ impl SPFParser for Iter<'_, u8> { |
2949 | } |
2950 | } |
2951 | |
2952 | - fn ip6(&mut self) -> super::Result<(Ipv6Addr, u8)> { |
2953 | + fn ip6(&mut self) -> crate::Result<(Ipv6Addr, u8)> { |
2954 | let mut stop_char = b' '; |
2955 | let mut ip = [0u16; 8]; |
2956 | let mut ip_pos = 0; |
2957 | @@ -498,7 +501,7 @@ impl SPFParser for Iter<'_, u8> { |
2958 | } |
2959 | } |
2960 | |
2961 | - fn cidr_length(&mut self) -> super::Result<u8> { |
2962 | + fn cidr_length(&mut self) -> crate::Result<u8> { |
2963 | let mut cidr_length: u8 = 0; |
2964 | for &ch in self { |
2965 | match ch { |
2966 | @@ -509,7 +512,7 @@ impl SPFParser for Iter<'_, u8> { |
2967 | if ch.is_ascii_whitespace() { |
2968 | break; |
2969 | } else { |
2970 | - return Err(Error::ParseFailed); |
2971 | + return Err(Error::ParseError); |
2972 | } |
2973 | } |
2974 | } |
2975 | @@ -518,7 +521,7 @@ impl SPFParser for Iter<'_, u8> { |
2976 | Ok(cidr_length) |
2977 | } |
2978 | |
2979 | - fn dual_cidr_length(&mut self) -> super::Result<(u8, u8)> { |
2980 | + fn dual_cidr_length(&mut self) -> crate::Result<(u8, u8)> { |
2981 | let mut ip4_length: u8 = u8::MAX; |
2982 | let mut ip6_length: u8 = u8::MAX; |
2983 | let mut in_ip6 = false; |
2984 | @@ -544,14 +547,14 @@ impl SPFParser for Iter<'_, u8> { |
2985 | if !in_ip6 { |
2986 | in_ip6 = true; |
2987 | } else if ip6_length != u8::MAX { |
2988 | - return Err(Error::ParseFailed); |
2989 | + return Err(Error::ParseError); |
2990 | } |
2991 | } |
2992 | _ => { |
2993 | if ch.is_ascii_whitespace() { |
2994 | break; |
2995 | } else { |
2996 | - return Err(Error::ParseFailed); |
2997 | + return Err(Error::ParseError); |
2998 | } |
2999 | } |
3000 | } |