Files
zeekayandHanzo Dev 15909c7b79 Rename Go module path to github.com/hanzoai/mail
Fork of github.com/mjl-/mox at 5656ca6d1b
(upstream v0.0.15 plus 55 commits).

Rename the module path in go.mod and the import paths in 268 Go files. Also
update the five scripts that "go install" the module (genwebsite.sh,
docker-release.sh, apidiff.sh, test-upgrade.sh, Dockerfile.release), which
would otherwise build upstream rather than this fork.

Add NOTICE recording the upstream project, author, license, and fork point,
and stating which license covers which files: MIT for the mox source and our
changes, MPL-2.0 for publicsuffix/public_suffix_list.txt alone, and the
vendored dependencies' own licenses under licenses/. Embed NOTICE so
"mail licenses" prints the lineage from the running binary. LICENSE.MIT,
LICENSE.MPLv2.0 and the licenses/ tree are preserved verbatim; no code is
relicensed.

The mox command name, config keys, data directory layout and website content
are unchanged. LLM.md records the fork point, the rename, what was left alone,
and the steps to stand the server up.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 23:16:19 -07:00

336 lines
10 KiB
Go

package tlsrptdb
import (
"context"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"time"
"github.com/hanzoai/mail/config"
"github.com/hanzoai/mail/dns"
"github.com/hanzoai/mail/mlog"
"github.com/hanzoai/mail/mox-"
"github.com/hanzoai/mail/tlsrpt"
)
var ctxbg = context.Background()
var pkglog = mlog.New("tlsrptdb", nil)
const reportJSON = `{
"organization-name": "Company-X",
"date-range": {
"start-datetime": "2016-04-01T00:00:00Z",
"end-datetime": "2016-04-01T23:59:59Z"
},
"contact-info": "[email protected]",
"report-id": "5065427c-23d3-47ca-b6e0-946ea0e8c4be",
"policies": [{
"policy": {
"policy-type": "sts",
"policy-string": ["version: STSv1","mode: testing",
"mx: *.mail.company-y.example","max_age: 86400"],
"policy-domain": "test.xmox.nl",
"mx-host": ["*.mail.company-y.example"]
},
"summary": {
"total-successful-session-count": 5326,
"total-failure-session-count": 303
},
"failure-details": [{
"result-type": "certificate-expired",
"sending-mta-ip": "2001:db8:abcd:0012::1",
"receiving-mx-hostname": "mx1.mail.company-y.example",
"failed-session-count": 100
}, {
"result-type": "starttls-not-supported",
"sending-mta-ip": "2001:db8:abcd:0013::1",
"receiving-mx-hostname": "mx2.mail.company-y.example",
"receiving-ip": "203.0.113.56",
"failed-session-count": 200,
"additional-information": "https://reports.company-x.example/report_info ? id = 5065427 c - 23 d3# StarttlsNotSupported "
}, {
"result-type": "validation-failure",
"sending-mta-ip": "198.51.100.62",
"receiving-ip": "203.0.113.58",
"receiving-mx-hostname": "mx-backup.mail.company-y.example",
"failed-session-count": 3,
"failure-reason-code": "X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED"
}]
}]
}`
const reportMultipleJSON = `{
"organization-name": "remote.example",
"date-range": {
"start-datetime": "2024-02-25T00:00:00Z",
"end-datetime": "2024-02-25T23:59:59Z"
},
"contact-info": "[email protected]",
"report-id": "[email protected]",
"policies": [
{
"policy": {
"policy-type": "tlsa",
"policy-string": [
"3 1 1 206d5f55ecb9f8389bc57b5ba14716dd5b23d0834fd2c99fd402f0bda32e9523",
"3 1 1 4201e4b741c746b62ff806c142158c35ecbbbd9ac56b6d791f760e272736f8d0"
],
"policy-domain": "test2.xmox.nl",
"mx-host": [
"mail.mox.example"
]
},
"summary": {
"total-successful-session-count": 1,
"total-failure-session-count": 0
},
"failure-details": []
},
{
"policy": {
"policy-type": "tlsa",
"policy-string": [
"3 1 1 206d5f55ecb9f8389bc57b5ba14716dd5b23d0834fd2c99fd402f0bda32e9523",
"3 1 1 4201e4b741c746b62ff806c142158c35ecbbbd9ac56b6d791f760e272736f8d0"
],
"policy-domain": "test.xmox.nl",
"mx-host": [
"mail.mox.example"
]
},
"summary": {
"total-successful-session-count": 1,
"total-failure-session-count": 0
},
"failure-details": []
}
]
}
`
const reportMixedJSON = `{
"organization-name": "remote.example",
"date-range": {
"start-datetime": "2024-02-25T00:00:00Z",
"end-datetime": "2024-02-25T23:59:59Z"
},
"contact-info": "[email protected]",
"report-id": "[email protected]",
"policies": [
{
"policy": {
"policy-type": "tlsa",
"policy-string": [
"3 1 1 206d5f55ecb9f8389bc57b5ba14716dd5b23d0834fd2c99fd402f0bda32e9523",
"3 1 1 4201e4b741c746b62ff806c142158c35ecbbbd9ac56b6d791f760e272736f8d0"
],
"policy-domain": "mail.mox.example",
"mx-host": []
},
"summary": {
"total-successful-session-count": 1,
"total-failure-session-count": 0
},
"failure-details": []
},
{
"policy": {
"policy-type": "sts",
"policy-string": [
"version: STSv1",
"mode: enforce",
"max_age: 86400",
"mx: mail.mox.example"
],
"policy-domain": "unknown.xmox.nl",
"mx-host": [
"mail.mox.example"
]
},
"summary": {
"total-successful-session-count": 1,
"total-failure-session-count": 0
},
"failure-details": []
},
{
"policy": {
"policy-type": "sts",
"policy-string": [
"version: STSv1",
"mode: enforce",
"max_age: 86400",
"mx: mail.mox.example"
],
"policy-domain": "test.xmox.nl",
"mx-host": [
"mail.mox.example"
]
},
"summary": {
"total-successful-session-count": 1,
"total-failure-session-count": 0
},
"failure-details": []
}
]
}
`
const reportUnknownJSON = `{
"organization-name": "remote.example",
"date-range": {
"start-datetime": "2024-02-25T00:00:00Z",
"end-datetime": "2024-02-25T23:59:59Z"
},
"contact-info": "[email protected]",
"report-id": "[email protected]",
"policies": [
{
"policy": {
"policy-type": "tlsa",
"policy-string": [
"3 1 1 206d5f55ecb9f8389bc57b5ba14716dd5b23d0834fd2c99fd402f0bda32e9523",
"3 1 1 4201e4b741c746b62ff806c142158c35ecbbbd9ac56b6d791f760e272736f8d0"
],
"policy-domain": "unknown.mox.example",
"mx-host": []
},
"summary": {
"total-successful-session-count": 1,
"total-failure-session-count": 0
},
"failure-details": []
},
{
"policy": {
"policy-type": "sts",
"policy-string": [
"version: STSv1",
"mode: enforce",
"max_age: 86400",
"mx: mail.mox.example"
],
"policy-domain": "unknown.xmox.nl",
"mx-host": [
"unknown.mox.example"
]
},
"summary": {
"total-successful-session-count": 1,
"total-failure-session-count": 0
},
"failure-details": []
}
]
}
`
func TestReport(t *testing.T) {
mox.Context = ctxbg
mox.Shutdown, mox.ShutdownCancel = context.WithCancel(ctxbg)
mox.ConfigStaticPath = filepath.FromSlash("../testdata/tlsrpt/fake.conf")
mox.Conf.Static.HostnameDomain = dns.Domain{ASCII: "mail.mox.example"}
mox.Conf.Static.DataDir = "."
// Recognize as configured domain.
mox.Conf.Dynamic.Domains = map[string]config.Domain{
"test.xmox.nl": {},
"test2.xmox.nl": {},
}
dbpath := mox.DataDirPath("tlsrpt.db")
os.MkdirAll(filepath.Dir(dbpath), 0770)
defer os.Remove(dbpath)
defer os.Remove(mox.DataDirPath("tlsrptresult.db"))
if err := Init(); err != nil {
t.Fatalf("init database: %s", err)
}
defer Close()
files, err := os.ReadDir("../testdata/tlsreports")
if err != nil {
t.Fatalf("listing reports: %s", err)
}
for _, file := range files {
f, err := os.Open("../testdata/tlsreports/" + file.Name())
if err != nil {
t.Fatalf("open %q: %s", file, err)
}
reportJSON, err := tlsrpt.ParseMessage(pkglog.Logger, f)
f.Close()
if err != nil {
t.Fatalf("parsing TLSRPT from message %q: %s", file.Name(), err)
}
report := reportJSON.Convert()
if err := AddReport(ctxbg, pkglog, dns.Domain{ASCII: "mox.example"}, "[email protected]", false, &report); err != nil {
t.Fatalf("adding report to database: %s", err)
}
}
reportJSON, err := tlsrpt.Parse(strings.NewReader(reportJSON))
if err != nil {
t.Fatalf("parsing report: %v", err)
}
report := reportJSON.Convert()
if err := AddReport(ctxbg, pkglog, dns.Domain{ASCII: "company-y.example"}, "[email protected]", false, &report); err != nil {
t.Fatalf("adding report to database: %s", err)
}
records, err := Records(ctxbg)
if err != nil {
t.Fatalf("fetching records: %s", err)
}
for _, r := range records {
if r.FromDomain != "company-y.example" {
continue
}
if !reflect.DeepEqual(r.Report, report) {
t.Fatalf("report, got %#v, expected %#v", r.Report, report)
}
if _, err := RecordID(ctxbg, r.ID); err != nil {
t.Fatalf("get record by id: %v", err)
}
}
start, _ := time.Parse(time.RFC3339, "2016-04-01T00:00:00Z")
end, _ := time.Parse(time.RFC3339, "2016-04-01T23:59:59Z")
records, err = RecordsPeriodDomain(ctxbg, start, end, dns.Domain{ASCII: "test.xmox.nl"})
if err != nil || len(records) != 1 {
t.Fatalf("got err %v, records %#v, expected no error with 1 record", err, records)
}
// Add report with multiple recipient domains.
reportJSON, err = tlsrpt.Parse(strings.NewReader(reportMultipleJSON))
if err != nil {
t.Fatalf("parsing report: %v", err)
}
report = reportJSON.Convert()
if err := AddReport(ctxbg, pkglog, dns.Domain{ASCII: "remote.example"}, "[email protected]", false, &report); err != nil {
t.Errorf("adding report to database: %s", err)
}
// Add report with mixed host and domain policies. The unknown domain is ignored.
reportJSON, err = tlsrpt.Parse(strings.NewReader(reportMixedJSON))
if err != nil {
t.Fatalf("parsing report: %v", err)
}
report = reportJSON.Convert()
if err := AddReport(ctxbg, pkglog, dns.Domain{ASCII: "remote.example"}, "[email protected]", false, &report); err != nil {
t.Errorf("adding report to database: %s", err)
}
// All unknown domains in report should cause error.
reportJSON, err = tlsrpt.Parse(strings.NewReader(reportUnknownJSON))
if err != nil {
t.Fatalf("parsing report: %v", err)
}
report = reportJSON.Convert()
if err := AddReport(ctxbg, pkglog, dns.Domain{ASCII: "remote.example"}, "[email protected]", false, &report); err == nil {
t.Errorf("adding report with all unknown domains, expected error")
}
}