1package tlsrptdb
2
3import (
4 "context"
5 "os"
6 "path/filepath"
7 "reflect"
8 "strings"
9 "testing"
10 "time"
11
12 "github.com/mjl-/mox/config"
13 "github.com/mjl-/mox/dns"
14 "github.com/mjl-/mox/mlog"
15 "github.com/mjl-/mox/mox-"
16 "github.com/mjl-/mox/tlsrpt"
17)
18
19var ctxbg = context.Background()
20var pkglog = mlog.New("tlsrptdb", nil)
21
22const reportJSON = `{
23 "organization-name": "Company-X",
24 "date-range": {
25 "start-datetime": "2016-04-01T00:00:00Z",
26 "end-datetime": "2016-04-01T23:59:59Z"
27 },
28 "contact-info": "sts-reporting@company-x.example",
29 "report-id": "5065427c-23d3-47ca-b6e0-946ea0e8c4be",
30 "policies": [{
31 "policy": {
32 "policy-type": "sts",
33 "policy-string": ["version: STSv1","mode: testing",
34 "mx: *.mail.company-y.example","max_age: 86400"],
35 "policy-domain": "test.xmox.nl",
36 "mx-host": ["*.mail.company-y.example"]
37 },
38 "summary": {
39 "total-successful-session-count": 5326,
40 "total-failure-session-count": 303
41 },
42 "failure-details": [{
43 "result-type": "certificate-expired",
44 "sending-mta-ip": "2001:db8:abcd:0012::1",
45 "receiving-mx-hostname": "mx1.mail.company-y.example",
46 "failed-session-count": 100
47 }, {
48 "result-type": "starttls-not-supported",
49 "sending-mta-ip": "2001:db8:abcd:0013::1",
50 "receiving-mx-hostname": "mx2.mail.company-y.example",
51 "receiving-ip": "203.0.113.56",
52 "failed-session-count": 200,
53 "additional-information": "https://reports.company-x.example/report_info ? id = 5065427 c - 23 d3# StarttlsNotSupported "
54 }, {
55 "result-type": "validation-failure",
56 "sending-mta-ip": "198.51.100.62",
57 "receiving-ip": "203.0.113.58",
58 "receiving-mx-hostname": "mx-backup.mail.company-y.example",
59 "failed-session-count": 3,
60 "failure-reason-code": "X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED"
61 }]
62 }]
63 }`
64
65const reportMultipleJSON = `{
66 "organization-name": "remote.example",
67 "date-range": {
68 "start-datetime": "2024-02-25T00:00:00Z",
69 "end-datetime": "2024-02-25T23:59:59Z"
70 },
71 "contact-info": "postmaster@remote.example",
72 "report-id": "20240225.mail.mox.example@remote.example",
73 "policies": [
74 {
75 "policy": {
76 "policy-type": "tlsa",
77 "policy-string": [
78 "3 1 1 206d5f55ecb9f8389bc57b5ba14716dd5b23d0834fd2c99fd402f0bda32e9523",
79 "3 1 1 4201e4b741c746b62ff806c142158c35ecbbbd9ac56b6d791f760e272736f8d0"
80 ],
81 "policy-domain": "test2.xmox.nl",
82 "mx-host": [
83 "mail.mox.example"
84 ]
85 },
86 "summary": {
87 "total-successful-session-count": 1,
88 "total-failure-session-count": 0
89 },
90 "failure-details": []
91 },
92 {
93 "policy": {
94 "policy-type": "tlsa",
95 "policy-string": [
96 "3 1 1 206d5f55ecb9f8389bc57b5ba14716dd5b23d0834fd2c99fd402f0bda32e9523",
97 "3 1 1 4201e4b741c746b62ff806c142158c35ecbbbd9ac56b6d791f760e272736f8d0"
98 ],
99 "policy-domain": "test.xmox.nl",
100 "mx-host": [
101 "mail.mox.example"
102 ]
103 },
104 "summary": {
105 "total-successful-session-count": 1,
106 "total-failure-session-count": 0
107 },
108 "failure-details": []
109 }
110 ]
111}
112`
113
114const reportMixedJSON = `{
115 "organization-name": "remote.example",
116 "date-range": {
117 "start-datetime": "2024-02-25T00:00:00Z",
118 "end-datetime": "2024-02-25T23:59:59Z"
119 },
120 "contact-info": "postmaster@remote.example",
121 "report-id": "20240225.test.xmox.nl@remote.example",
122 "policies": [
123 {
124 "policy": {
125 "policy-type": "tlsa",
126 "policy-string": [
127 "3 1 1 206d5f55ecb9f8389bc57b5ba14716dd5b23d0834fd2c99fd402f0bda32e9523",
128 "3 1 1 4201e4b741c746b62ff806c142158c35ecbbbd9ac56b6d791f760e272736f8d0"
129 ],
130 "policy-domain": "mail.mox.example",
131 "mx-host": []
132 },
133 "summary": {
134 "total-successful-session-count": 1,
135 "total-failure-session-count": 0
136 },
137 "failure-details": []
138 },
139 {
140 "policy": {
141 "policy-type": "sts",
142 "policy-string": [
143 "version: STSv1",
144 "mode: enforce",
145 "max_age: 86400",
146 "mx: mail.mox.example"
147 ],
148 "policy-domain": "unknown.xmox.nl",
149 "mx-host": [
150 "mail.mox.example"
151 ]
152 },
153 "summary": {
154 "total-successful-session-count": 1,
155 "total-failure-session-count": 0
156 },
157 "failure-details": []
158 },
159 {
160 "policy": {
161 "policy-type": "sts",
162 "policy-string": [
163 "version: STSv1",
164 "mode: enforce",
165 "max_age: 86400",
166 "mx: mail.mox.example"
167 ],
168 "policy-domain": "test.xmox.nl",
169 "mx-host": [
170 "mail.mox.example"
171 ]
172 },
173 "summary": {
174 "total-successful-session-count": 1,
175 "total-failure-session-count": 0
176 },
177 "failure-details": []
178 }
179 ]
180}
181`
182
183const reportUnknownJSON = `{
184 "organization-name": "remote.example",
185 "date-range": {
186 "start-datetime": "2024-02-25T00:00:00Z",
187 "end-datetime": "2024-02-25T23:59:59Z"
188 },
189 "contact-info": "postmaster@remote.example",
190 "report-id": "20240225.test.xmox.nl@remote.example",
191 "policies": [
192 {
193 "policy": {
194 "policy-type": "tlsa",
195 "policy-string": [
196 "3 1 1 206d5f55ecb9f8389bc57b5ba14716dd5b23d0834fd2c99fd402f0bda32e9523",
197 "3 1 1 4201e4b741c746b62ff806c142158c35ecbbbd9ac56b6d791f760e272736f8d0"
198 ],
199 "policy-domain": "unknown.mox.example",
200 "mx-host": []
201 },
202 "summary": {
203 "total-successful-session-count": 1,
204 "total-failure-session-count": 0
205 },
206 "failure-details": []
207 },
208 {
209 "policy": {
210 "policy-type": "sts",
211 "policy-string": [
212 "version: STSv1",
213 "mode: enforce",
214 "max_age: 86400",
215 "mx: mail.mox.example"
216 ],
217 "policy-domain": "unknown.xmox.nl",
218 "mx-host": [
219 "unknown.mox.example"
220 ]
221 },
222 "summary": {
223 "total-successful-session-count": 1,
224 "total-failure-session-count": 0
225 },
226 "failure-details": []
227 }
228 ]
229}
230`
231
232func TestReport(t *testing.T) {
233 mox.Context = ctxbg
234 mox.Shutdown, mox.ShutdownCancel = context.WithCancel(ctxbg)
235 mox.ConfigStaticPath = filepath.FromSlash("../testdata/tlsrpt/fake.conf")
236 mox.Conf.Static.HostnameDomain = dns.Domain{ASCII: "mail.mox.example"}
237 mox.Conf.Static.DataDir = "."
238 // Recognize as configured domain.
239 mox.Conf.Dynamic.Domains = map[string]config.Domain{
240 "test.xmox.nl": {},
241 "test2.xmox.nl": {},
242 }
243
244 dbpath := mox.DataDirPath("tlsrpt.db")
245 os.MkdirAll(filepath.Dir(dbpath), 0770)
246 defer os.Remove(dbpath)
247 defer os.Remove(mox.DataDirPath("tlsrptresult.db"))
248
249 if err := Init(); err != nil {
250 t.Fatalf("init database: %s", err)
251 }
252 defer Close()
253
254 files, err := os.ReadDir("../testdata/tlsreports")
255 if err != nil {
256 t.Fatalf("listing reports: %s", err)
257 }
258 for _, file := range files {
259 f, err := os.Open("../testdata/tlsreports/" + file.Name())
260 if err != nil {
261 t.Fatalf("open %q: %s", file, err)
262 }
263 reportJSON, err := tlsrpt.ParseMessage(pkglog.Logger, f)
264 f.Close()
265 if err != nil {
266 t.Fatalf("parsing TLSRPT from message %q: %s", file.Name(), err)
267 }
268 report := reportJSON.Convert()
269 if err := AddReport(ctxbg, pkglog, dns.Domain{ASCII: "mox.example"}, "tlsrpt@mox.example", false, &report); err != nil {
270 t.Fatalf("adding report to database: %s", err)
271 }
272 }
273
274 reportJSON, err := tlsrpt.Parse(strings.NewReader(reportJSON))
275 if err != nil {
276 t.Fatalf("parsing report: %v", err)
277 }
278 report := reportJSON.Convert()
279 if err := AddReport(ctxbg, pkglog, dns.Domain{ASCII: "company-y.example"}, "tlsrpt@company-y.example", false, &report); err != nil {
280 t.Fatalf("adding report to database: %s", err)
281 }
282
283 records, err := Records(ctxbg)
284 if err != nil {
285 t.Fatalf("fetching records: %s", err)
286 }
287 for _, r := range records {
288 if r.FromDomain != "company-y.example" {
289 continue
290 }
291 if !reflect.DeepEqual(r.Report, report) {
292 t.Fatalf("report, got %#v, expected %#v", r.Report, report)
293 }
294 if _, err := RecordID(ctxbg, r.ID); err != nil {
295 t.Fatalf("get record by id: %v", err)
296 }
297 }
298
299 start, _ := time.Parse(time.RFC3339, "2016-04-01T00:00:00Z")
300 end, _ := time.Parse(time.RFC3339, "2016-04-01T23:59:59Z")
301 records, err = RecordsPeriodDomain(ctxbg, start, end, dns.Domain{ASCII: "test.xmox.nl"})
302 if err != nil || len(records) != 1 {
303 t.Fatalf("got err %v, records %#v, expected no error with 1 record", err, records)
304 }
305
306 // Add report with multiple recipient domains.
307 reportJSON, err = tlsrpt.Parse(strings.NewReader(reportMultipleJSON))
308 if err != nil {
309 t.Fatalf("parsing report: %v", err)
310 }
311 report = reportJSON.Convert()
312 if err := AddReport(ctxbg, pkglog, dns.Domain{ASCII: "remote.example"}, "postmaster@remote.example", false, &report); err != nil {
313 t.Errorf("adding report to database: %s", err)
314 }
315
316 // Add report with mixed host and domain policies. The unknown domain is ignored.
317 reportJSON, err = tlsrpt.Parse(strings.NewReader(reportMixedJSON))
318 if err != nil {
319 t.Fatalf("parsing report: %v", err)
320 }
321 report = reportJSON.Convert()
322 if err := AddReport(ctxbg, pkglog, dns.Domain{ASCII: "remote.example"}, "postmaster@remote.example", false, &report); err != nil {
323 t.Errorf("adding report to database: %s", err)
324 }
325
326 // All unknown domains in report should cause error.
327 reportJSON, err = tlsrpt.Parse(strings.NewReader(reportUnknownJSON))
328 if err != nil {
329 t.Fatalf("parsing report: %v", err)
330 }
331 report = reportJSON.Convert()
332 if err := AddReport(ctxbg, pkglog, dns.Domain{ASCII: "remote.example"}, "postmaster@remote.example", false, &report); err == nil {
333 t.Errorf("adding report with all unknown domains, expected error")
334 }
335}
336