diff --git a/webadmin/admin.go b/webadmin/admin.go index 7371baa..9d4f821 100644 --- a/webadmin/admin.go +++ b/webadmin/admin.go @@ -1463,6 +1463,13 @@ func (Admin) Domain(ctx context.Context, domain string) dns.Domain { return d } +// ParseDomain parses a domain, possibly an IDNA domain. +func (Admin) ParseDomain(ctx context.Context, domain string) dns.Domain { + d, err := dns.ParseDomain(domain) + xcheckuserf(ctx, err, "parse domain") + return d +} + // DomainLocalparts returns the encoded localparts and accounts configured in domain. func (Admin) DomainLocalparts(ctx context.Context, domain string) (localpartAccounts map[string]string) { d, err := dns.ParseDomain(domain) @@ -1559,7 +1566,7 @@ type TLSRPTSummary struct { PolicyDomain dns.Domain Success int64 Failure int64 - ResultTypeCounts map[tlsrpt.ResultType]int + ResultTypeCounts map[tlsrpt.ResultType]int64 } // TLSRPTSummaries returns a summary of received TLS reports overlapping with @@ -1587,9 +1594,9 @@ func (Admin) TLSRPTSummaries(ctx context.Context, start, end time.Time, policyDo sum.Failure += result.Summary.TotalFailureSessionCount for _, details := range result.FailureDetails { if sum.ResultTypeCounts == nil { - sum.ResultTypeCounts = map[tlsrpt.ResultType]int{} + sum.ResultTypeCounts = map[tlsrpt.ResultType]int64{} } - sum.ResultTypeCounts[details.ResultType]++ + sum.ResultTypeCounts[details.ResultType] += details.FailedSessionCount } } summaries[dom] = sum diff --git a/webadmin/admin.html b/webadmin/admin.html index 9e03645..eba983f 100644 --- a/webadmin/admin.html +++ b/webadmin/admin.html @@ -1465,7 +1465,7 @@ const tlsrptIndex = async () => { dom._kids(page, crumbs( crumblink('Mox Admin', '#'), - 'TLSRPT reports and connectivity results', + 'TLSRPT', ), dom.ul( dom.li( @@ -1620,7 +1620,8 @@ const tlsrptReports = async () => { dom._kids(page, crumbs( crumblink('Mox Admin', '#'), - 'TLS reports (TLSRPT)', + crumblink('TLSRPT', '#tlsrpt'), + 'Reports' ), dom.p('TLSRPT (TLS reporting) is a mechanism to request feedback from other mail servers about TLS connections to your mail server. If is typically used along with MTA-STS and/or DANE to enforce that SMTP connections are protected with TLS. Mail servers implementing TLSRPT will typically send a daily report with both successful and failed connection counts, including details about failures.'), renderTLSRPTSummaries(summaries) @@ -1643,7 +1644,7 @@ const renderTLSRPTSummaries = (summaries) => { dom.tbody( summaries.map(r => dom.tr( - dom.td(dom.a(attr({href: '#domains/' + domainName(r.PolicyDomain) + '/tlsrpt', title: 'See report details.'}), domainName(r.PolicyDomain))), + dom.td(dom.a(attr({href: '#tlsrpt/reports/' + domainName(r.PolicyDomain), title: 'See report details.'}), domainName(r.PolicyDomain))), dom.td(style({textAlign: 'right'}), '' + r.Success), dom.td(style({textAlign: 'right'}), '' + r.Failure), dom.td(!r.ResultTypeCounts ? [] : Object.entries(r.ResultTypeCounts).map(kv => kv[0] + ': ' + kv[1]).join('; ')), @@ -1659,7 +1660,7 @@ const domainTLSRPT = async (d) => { const start = new Date(new Date().getTime() - 30*24*3600*1000).toISOString() const [records, dnsdomain] = await Promise.all([ api.TLSReports(start, end, d), - api.Domain(d), + api.ParseDomain(d), ]) const policyType = (policy) => { @@ -1677,8 +1678,9 @@ const domainTLSRPT = async (d) => { dom._kids(page, crumbs( crumblink('Mox Admin', '#'), - crumblink('Domain ' + domainString(dnsdomain), '#domains/'+d), - 'TLSRPT', + crumblink('TLSRPT', '#tlsrpt'), + crumblink('Reports', '#tlsrpt/reports'), + 'Domain '+domainString(dnsdomain), ), dom.p('TLSRPT (TLS reporting) is a mechanism to request feedback from other mail servers about TLS connections to your mail server. If is typically used along with MTA-STS and/or DANE to enforce that SMTP connections are protected with TLS. Mail servers implementing TLSRPT will typically send a daily report with both successful and failed connection counts, including details about failures.'), dom.p('Below the TLS reports for the past 30 days.'), @@ -1724,7 +1726,7 @@ const domainTLSRPT = async (d) => { const addRow = (d, di) => { const row = dom.tr( index > 0 || rows.length > 0 ? [] : [ - dom.td(reportRowSpan, valignTop, dom.a(''+record.ID, attr({href: '#domains/' + record.Domain + '/tlsrpt/'+record.ID}))), + dom.td(reportRowSpan, valignTop, dom.a(''+record.ID, attr({href: '#tlsrpt/reports/' + record.Domain + '/' + record.ID}))), dom.td(reportRowSpan, valignTop, r['organization-name'] || r['contact-info'] || record.MailFrom || '', attr({title: 'Organization: ' +r['organization-name'] + '; \nContact info: ' + r['contact-info'] + '; \nReport ID: ' + r['report-id'] + '; \nMail from: ' + record.MailFrom, })), dom.td(reportRowSpan, valignTop, period(new Date(r['date-range']['start-datetime']), new Date(r['date-range']['end-datetime']))), ], @@ -1766,15 +1768,16 @@ const domainTLSRPT = async (d) => { const domainTLSRPTID = async (d, reportID) => { const [report, dnsdomain] = await Promise.all([ api.TLSReportID(d, reportID), - api.Domain(d), + api.ParseDomain(d), ]) const page = document.getElementById('page') dom._kids(page, crumbs( crumblink('Mox Admin', '#'), - crumblink('Domain ' + domainString(dnsdomain), '#domains/'+d), - crumblink('TLS report', '#domains/' + d + '/tlsrpt'), + crumblink('TLSRPT', '#tlsrpt'), + crumblink('Reports', '#tlsrpt/reports'), + crumblink('Domain '+domainString(dnsdomain), '#tlsrpt/reports/' + d + ''), 'Report ' + reportID ), dom.p('Below is the raw report as received from the remote mail server.'), @@ -2588,10 +2591,6 @@ const init = async () => { await domainDMARC(t[1]) } else if (t[0] === 'domains' && t.length === 4 && t[2] === 'dmarc' && parseInt(t[3])) { await domainDMARCReport(t[1], parseInt(t[3])) - } else if (t[0] === 'domains' && t.length === 3 && t[2] === 'tlsrpt') { - await domainTLSRPT(t[1]) - } else if (t[0] === 'domains' && t.length === 4 && t[2] === 'tlsrpt' && parseInt(t[3])) { - await domainTLSRPTID(t[1], parseInt(t[3])) } else if (t[0] === 'domains' && t.length === 3 && t[2] === 'dnscheck') { await domainDNSCheck(t[1]) } else if (t[0] === 'domains' && t.length === 3 && t[2] === 'dnsrecords') { @@ -2602,6 +2601,10 @@ const init = async () => { await tlsrptIndex() } else if (h === 'tlsrpt/reports') { await tlsrptReports() + } else if (t[0] === 'tlsrpt' && t[1] === 'reports' && t.length === 3) { + await domainTLSRPT(t[2]) + } else if (t[0] === 'tlsrpt' && t[1] === 'reports' && t.length === 4 && parseInt(t[3])) { + await domainTLSRPTID(t[2], parseInt(t[3])) } else if (h === 'tlsrpt/results') { await tlsrptResults() } else if (t[0] == 'tlsrpt' && t[1] == 'results' && t.length === 3) { diff --git a/webadmin/adminapi.json b/webadmin/adminapi.json index c6d17a3..fd8c8ce 100644 --- a/webadmin/adminapi.json +++ b/webadmin/adminapi.json @@ -56,6 +56,26 @@ } ] }, + { + "Name": "ParseDomain", + "Docs": "ParseDomain parses a domain, possibly an IDNA domain.", + "Params": [ + { + "Name": "domain", + "Typewords": [ + "string" + ] + } + ], + "Returns": [ + { + "Name": "r0", + "Typewords": [ + "Domain" + ] + } + ] + }, { "Name": "DomainLocalparts", "Docs": "DomainLocalparts returns the encoded localparts and accounts configured in domain.", @@ -2545,7 +2565,7 @@ "Docs": "", "Typewords": [ "{}", - "int32" + "int64" ] } ]