mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 16:33:47 +03:00
add suppression list for outgoing dmarc and tls reports
for reporting addresses that cause DSNs to be returned. that just adds noise. the admin can add/remove/extend addresses through the webadmin. in the future, we could send reports with a smtp mail from of "postmaster+<signed-encoded-recipient>@...", and add the reporting recipient on the suppression list automatically when a DSN comes in on that address, but for now this will probably do.
This commit is contained in:
parent
6ce69d5425
commit
e24e1bee19
12 changed files with 697 additions and 17 deletions
|
@ -60,7 +60,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
EvalDBTypes = []any{Evaluation{}} // Types stored in DB.
|
EvalDBTypes = []any{Evaluation{}, SuppressAddress{}} // Types stored in DB.
|
||||||
// Exported for backups. For incoming deliveries the SMTP server adds evaluations
|
// Exported for backups. For incoming deliveries the SMTP server adds evaluations
|
||||||
// to the database. Every hour, a goroutine wakes up that gathers evaluations from
|
// to the database. Every hour, a goroutine wakes up that gathers evaluations from
|
||||||
// the last hour(s), sends a report, and removes the evaluations from the database.
|
// the last hour(s), sends a report, and removes the evaluations from the database.
|
||||||
|
@ -119,6 +119,16 @@ type Evaluation struct {
|
||||||
SPFResults []dmarcrpt.SPFAuthResult
|
SPFResults []dmarcrpt.SPFAuthResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SuppressAddress is a reporting address for which outgoing DMARC reports
|
||||||
|
// will be suppressed for a period.
|
||||||
|
type SuppressAddress struct {
|
||||||
|
ID int64
|
||||||
|
Inserted time.Time `bstore:"default now"`
|
||||||
|
ReportingAddress string `bstore:"unique"`
|
||||||
|
Until time.Time `bstore:"nonzero"`
|
||||||
|
Comment string
|
||||||
|
}
|
||||||
|
|
||||||
var dmarcResults = map[bool]dmarcrpt.DMARCResult{
|
var dmarcResults = map[bool]dmarcrpt.DMARCResult{
|
||||||
false: dmarcrpt.DMARCFail,
|
false: dmarcrpt.DMARCFail,
|
||||||
true: dmarcrpt.DMARCPass,
|
true: dmarcrpt.DMARCPass,
|
||||||
|
@ -803,6 +813,19 @@ Period: %s - %s UTC
|
||||||
msgSize := int64(len(msgPrefix)) + msgInfo.Size()
|
msgSize := int64(len(msgPrefix)) + msgInfo.Size()
|
||||||
var queued bool
|
var queued bool
|
||||||
for _, rcpt := range recipients {
|
for _, rcpt := range recipients {
|
||||||
|
// If recipient is on suppression list, we won't queue the reporting message.
|
||||||
|
q := bstore.QueryDB[SuppressAddress](ctx, db)
|
||||||
|
q.FilterNonzero(SuppressAddress{ReportingAddress: rcpt.address.Path().String()})
|
||||||
|
q.FilterGreater("Until", time.Now())
|
||||||
|
exists, err := q.Exists()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("querying suppress list: %v", err)
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
log.Info("suppressing outgoing dmarc aggregate report", mlog.Field("reportingaddress", rcpt.address))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Only send to addresses where we don't exceed their size limit. The RFC mentions
|
// Only send to addresses where we don't exceed their size limit. The RFC mentions
|
||||||
// the size of the report, but then continues about the size after compression and
|
// the size of the report, but then continues about the size after compression and
|
||||||
// transport encodings (i.e. gzip and the mime base64 attachment, so the intention
|
// transport encodings (i.e. gzip and the mime base64 attachment, so the intention
|
||||||
|
@ -818,7 +841,7 @@ Period: %s - %s UTC
|
||||||
qm.MaxAttempts = 5
|
qm.MaxAttempts = 5
|
||||||
qm.IsDMARCReport = true
|
qm.IsDMARCReport = true
|
||||||
|
|
||||||
err := queueAdd(ctx, log, &qm, msgf)
|
err = queueAdd(ctx, log, &qm, msgf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tempError = true
|
tempError = true
|
||||||
log.Errorx("queueing message with dmarc aggregate report", err)
|
log.Errorx("queueing message with dmarc aggregate report", err)
|
||||||
|
@ -831,7 +854,7 @@ Period: %s - %s UTC
|
||||||
}
|
}
|
||||||
|
|
||||||
if !queued {
|
if !queued {
|
||||||
if err := sendErrorReport(ctx, log, from, addrs, dom, report.ReportMetadata.ReportID, msgSize); err != nil {
|
if err := sendErrorReport(ctx, log, db, from, addrs, dom, report.ReportMetadata.ReportID, msgSize); err != nil {
|
||||||
log.Errorx("sending dmarc error reports", err)
|
log.Errorx("sending dmarc error reports", err)
|
||||||
metricReportError.Inc()
|
metricReportError.Inc()
|
||||||
}
|
}
|
||||||
|
@ -917,7 +940,7 @@ func composeAggregateReport(ctx context.Context, log *mlog.Log, mf *os.File, fro
|
||||||
// Though this functionality is quite underspecified, we'll do our best to send our
|
// Though this functionality is quite underspecified, we'll do our best to send our
|
||||||
// an error report in case our report is too large for all recipients.
|
// an error report in case our report is too large for all recipients.
|
||||||
// ../rfc/7489:1918
|
// ../rfc/7489:1918
|
||||||
func sendErrorReport(ctx context.Context, log *mlog.Log, fromAddr smtp.Address, recipients []message.NameAddress, reportDomain dns.Domain, reportID string, reportMsgSize int64) error {
|
func sendErrorReport(ctx context.Context, log *mlog.Log, db *bstore.DB, fromAddr smtp.Address, recipients []message.NameAddress, reportDomain dns.Domain, reportID string, reportMsgSize int64) error {
|
||||||
log.Debug("no reporting addresses willing to accept report given size, queuing short error message")
|
log.Debug("no reporting addresses willing to accept report given size, queuing short error message")
|
||||||
|
|
||||||
msgf, err := store.CreateMessageTemp("dmarcreportmsg-out")
|
msgf, err := store.CreateMessageTemp("dmarcreportmsg-out")
|
||||||
|
@ -954,6 +977,19 @@ Submitting-URI: %s
|
||||||
msgSize := int64(len(msgPrefix)) + msgInfo.Size()
|
msgSize := int64(len(msgPrefix)) + msgInfo.Size()
|
||||||
|
|
||||||
for _, rcpt := range recipients {
|
for _, rcpt := range recipients {
|
||||||
|
// If recipient is on suppression list, we won't queue the reporting message.
|
||||||
|
q := bstore.QueryDB[SuppressAddress](ctx, db)
|
||||||
|
q.FilterNonzero(SuppressAddress{ReportingAddress: rcpt.Address.Path().String()})
|
||||||
|
q.FilterGreater("Until", time.Now())
|
||||||
|
exists, err := q.Exists()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("querying suppress list: %v", err)
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
log.Info("suppressing outgoing dmarc error report", mlog.Field("reportingaddress", rcpt.Address))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
qm := queue.MakeMsg(mox.Conf.Static.Postmaster.Account, fromAddr.Path(), rcpt.Address.Path(), has8bit, smtputf8, msgSize, messageID, []byte(msgPrefix), nil)
|
qm := queue.MakeMsg(mox.Conf.Static.Postmaster.Account, fromAddr.Path(), rcpt.Address.Path(), has8bit, smtputf8, msgSize, messageID, []byte(msgPrefix), nil)
|
||||||
// Don't try as long as regular deliveries, and stop before we would send the
|
// Don't try as long as regular deliveries, and stop before we would send the
|
||||||
// delayed DSN. Though we also won't send that due to IsDMARCReport.
|
// delayed DSN. Though we also won't send that due to IsDMARCReport.
|
||||||
|
@ -1044,3 +1080,49 @@ func dkimSign(ctx context.Context, log *mlog.Log, fromAddr smtp.Address, smtputf
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SuppressAdd adds an address to the suppress list.
|
||||||
|
func SuppressAdd(ctx context.Context, ba *SuppressAddress) error {
|
||||||
|
db, err := evalDB(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.Insert(ctx, ba)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuppressList returns all reporting addresses on the suppress list.
|
||||||
|
func SuppressList(ctx context.Context) ([]SuppressAddress, error) {
|
||||||
|
db, err := evalDB(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bstore.QueryDB[SuppressAddress](ctx, db).SortDesc("ID").List()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuppressRemove removes a reporting address record from the suppress list.
|
||||||
|
func SuppressRemove(ctx context.Context, id int64) error {
|
||||||
|
db, err := evalDB(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.Delete(ctx, &SuppressAddress{ID: id})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuppressUpdate updates the until field of a reporting address record.
|
||||||
|
func SuppressUpdate(ctx context.Context, id int64, until time.Time) error {
|
||||||
|
db, err := evalDB(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ba := SuppressAddress{ID: id}
|
||||||
|
err = db.Get(ctx, &ba)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ba.Until = until
|
||||||
|
return db.Update(ctx, &ba)
|
||||||
|
}
|
||||||
|
|
|
@ -312,7 +312,7 @@ func TestSendReports(t *testing.T) {
|
||||||
|
|
||||||
if optExpReport != nil {
|
if optExpReport != nil {
|
||||||
// Parse report in message and compare with expected.
|
// Parse report in message and compare with expected.
|
||||||
expFeedback.ReportMetadata.ReportID = feedback.ReportMetadata.ReportID
|
optExpReport.ReportMetadata.ReportID = feedback.ReportMetadata.ReportID
|
||||||
tcompare(t, feedback, expFeedback)
|
tcompare(t, feedback, expFeedback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,6 +348,18 @@ func TestSendReports(t *testing.T) {
|
||||||
evalOpt.Optional = true
|
evalOpt.Optional = true
|
||||||
test([]Evaluation{evalOpt}, map[string]struct{}{}, map[string]struct{}{}, nil)
|
test([]Evaluation{evalOpt}, map[string]struct{}{}, map[string]struct{}{}, nil)
|
||||||
|
|
||||||
|
// Address is suppressed.
|
||||||
|
sa := SuppressAddress{ReportingAddress: "dmarcrpt@sender.example", Until: time.Now().Add(time.Minute)}
|
||||||
|
err = db.Insert(ctxbg, &sa)
|
||||||
|
tcheckf(t, err, "insert suppress address")
|
||||||
|
test([]Evaluation{eval}, map[string]struct{}{}, map[string]struct{}{}, nil)
|
||||||
|
|
||||||
|
// Suppression has expired.
|
||||||
|
sa.Until = time.Now().Add(-time.Minute)
|
||||||
|
err = db.Update(ctxbg, &sa)
|
||||||
|
tcheckf(t, err, "update suppress address")
|
||||||
|
test([]Evaluation{eval}, map[string]struct{}{"dmarcrpt@sender.example": {}}, map[string]struct{}{}, expFeedback)
|
||||||
|
|
||||||
// Two RUA's, one with a size limit that doesn't pass, and one that does pass.
|
// Two RUA's, one with a size limit that doesn't pass, and one that does pass.
|
||||||
resolver.TXT["_dmarc.sender.example."] = []string{"v=DMARC1; rua=mailto:dmarcrpt1@sender.example!1,mailto:dmarcrpt2@sender.example!10t; ri=3600"}
|
resolver.TXT["_dmarc.sender.example."] = []string{"v=DMARC1; rua=mailto:dmarcrpt1@sender.example!1,mailto:dmarcrpt2@sender.example!10t; ri=3600"}
|
||||||
test([]Evaluation{eval}, map[string]struct{}{"dmarcrpt2@sender.example": {}}, map[string]struct{}{}, nil)
|
test([]Evaluation{eval}, map[string]struct{}{"dmarcrpt2@sender.example": {}}, map[string]struct{}{}, nil)
|
||||||
|
|
|
@ -605,6 +605,20 @@ func deliver(resolver dns.Resolver, m Msg) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
dayUTC := now.UTC().Format("20060102")
|
dayUTC := now.UTC().Format("20060102")
|
||||||
|
|
||||||
|
// See if this contains a failure. If not, we'll mark TLS results for delivering
|
||||||
|
// DMARC reports SendReport false, so we won't as easily get into a report sending
|
||||||
|
// loop.
|
||||||
|
var failure bool
|
||||||
|
for _, result := range hostResults {
|
||||||
|
if result.Summary.TotalFailureSessionCount > 0 {
|
||||||
|
failure = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if recipientDomainResult.Summary.TotalFailureSessionCount > 0 {
|
||||||
|
failure = true
|
||||||
|
}
|
||||||
|
|
||||||
results := make([]tlsrptdb.TLSResult, 0, 1+len(hostResults))
|
results := make([]tlsrptdb.TLSResult, 0, 1+len(hostResults))
|
||||||
tlsaPolicyDomains := map[string]bool{}
|
tlsaPolicyDomains := map[string]bool{}
|
||||||
addResult := func(r tlsrpt.Result, isHost bool) {
|
addResult := func(r tlsrpt.Result, isHost bool) {
|
||||||
|
@ -629,7 +643,7 @@ func deliver(resolver dns.Resolver, m Msg) {
|
||||||
DayUTC: dayUTC,
|
DayUTC: dayUTC,
|
||||||
RecipientDomain: m.RecipientDomain.Domain.Name(),
|
RecipientDomain: m.RecipientDomain.Domain.Name(),
|
||||||
IsHost: isHost,
|
IsHost: isHost,
|
||||||
SendReport: !m.IsTLSReport,
|
SendReport: !m.IsTLSReport && (!m.IsDMARCReport || failure),
|
||||||
Results: []tlsrpt.Result{r},
|
Results: []tlsrpt.Result{r},
|
||||||
}
|
}
|
||||||
results = append(results, tlsResult)
|
results = append(results, tlsResult)
|
||||||
|
|
|
@ -985,13 +985,12 @@ func TestTLSReport(t *testing.T) {
|
||||||
ts := newTestServer(t, filepath.FromSlash("../testdata/smtp/tlsrpt/mox.conf"), resolver)
|
ts := newTestServer(t, filepath.FromSlash("../testdata/smtp/tlsrpt/mox.conf"), resolver)
|
||||||
defer ts.close()
|
defer ts.close()
|
||||||
|
|
||||||
run := func(tlsrpt string, n int) {
|
run := func(rcptTo, tlsrpt string, n int) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
ts.run(func(err error, client *smtpclient.Client) {
|
ts.run(func(err error, client *smtpclient.Client) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
mailFrom := "remote@example.org"
|
mailFrom := "remote@example.org"
|
||||||
rcptTo := "mjl@mox.example"
|
|
||||||
|
|
||||||
msgb := &bytes.Buffer{}
|
msgb := &bytes.Buffer{}
|
||||||
_, xerr := fmt.Fprintf(msgb, "From: %s\r\nTo: %s\r\nSubject: tlsrpt report\r\nMIME-Version: 1.0\r\nContent-Type: application/tlsrpt+json\r\n\r\n%s\r\n", mailFrom, rcptTo, tlsrpt)
|
_, xerr := fmt.Fprintf(msgb, "From: %s\r\nTo: %s\r\nSubject: tlsrpt report\r\nMIME-Version: 1.0\r\nContent-Type: application/tlsrpt+json\r\n\r\n%s\r\n", mailFrom, rcptTo, tlsrpt)
|
||||||
|
@ -1017,13 +1016,15 @@ func TestTLSReport(t *testing.T) {
|
||||||
|
|
||||||
const tlsrpt = `{"organization-name":"Example.org","date-range":{"start-datetime":"2022-01-07T00:00:00Z","end-datetime":"2022-01-07T23:59:59Z"},"contact-info":"tlsrpt@example.org","report-id":"1","policies":[{"policy":{"policy-type":"no-policy-found","policy-domain":"xmox.nl"},"summary":{"total-successful-session-count":1,"total-failure-session-count":0}}]}`
|
const tlsrpt = `{"organization-name":"Example.org","date-range":{"start-datetime":"2022-01-07T00:00:00Z","end-datetime":"2022-01-07T23:59:59Z"},"contact-info":"tlsrpt@example.org","report-id":"1","policies":[{"policy":{"policy-type":"no-policy-found","policy-domain":"xmox.nl"},"summary":{"total-successful-session-count":1,"total-failure-session-count":0}}]}`
|
||||||
|
|
||||||
run(tlsrpt, 0)
|
run("mjl@mox.example", tlsrpt, 0)
|
||||||
run(strings.ReplaceAll(tlsrpt, "xmox.nl", "mox.example"), 1)
|
run("mjl@mox.example", strings.ReplaceAll(tlsrpt, "xmox.nl", "mox.example"), 1)
|
||||||
|
run("mjl@mailhost.mox.example", strings.ReplaceAll(tlsrpt, "xmox.nl", "mailhost.mox.example"), 2)
|
||||||
|
|
||||||
// We always store as an evaluation, but as optional for reports.
|
// We always store as an evaluation, but as optional for reports.
|
||||||
evals := checkEvaluationCount(t, 2)
|
evals := checkEvaluationCount(t, 3)
|
||||||
tcompare(t, evals[0].Optional, true)
|
tcompare(t, evals[0].Optional, true)
|
||||||
tcompare(t, evals[1].Optional, true)
|
tcompare(t, evals[1].Optional, true)
|
||||||
|
tcompare(t, evals[2].Optional, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRatelimitConnectionrate(t *testing.T) {
|
func TestRatelimitConnectionrate(t *testing.T) {
|
||||||
|
|
6
testdata/smtp/tlsrpt/mox.conf
vendored
6
testdata/smtp/tlsrpt/mox.conf
vendored
|
@ -1,9 +1,13 @@
|
||||||
DataDir: ../data
|
DataDir: ../data
|
||||||
User: 1000
|
User: 1000
|
||||||
LogLevel: trace
|
LogLevel: trace
|
||||||
Hostname: mox.example
|
Hostname: mailhost.mox.example
|
||||||
Postmaster:
|
Postmaster:
|
||||||
Account: mjl
|
Account: mjl
|
||||||
Mailbox: postmaster
|
Mailbox: postmaster
|
||||||
Listeners:
|
Listeners:
|
||||||
local: nil
|
local: nil
|
||||||
|
HostTLSRPT:
|
||||||
|
Account: mjl
|
||||||
|
Mailbox: TLSRPT
|
||||||
|
Localpart: mjl
|
||||||
|
|
|
@ -17,7 +17,7 @@ var (
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
|
|
||||||
// Accessed directly by tlsrptsend.
|
// Accessed directly by tlsrptsend.
|
||||||
ResultDBTypes = []any{TLSResult{}}
|
ResultDBTypes = []any{TLSResult{}, TLSRPTSuppressAddress{}}
|
||||||
ResultDB *bstore.DB
|
ResultDB *bstore.DB
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,18 @@ type TLSResult struct {
|
||||||
Results []tlsrpt.Result
|
Results []tlsrpt.Result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: TLSRPTSuppressAddress should be named just SuppressAddress, but would clash with dmarcdb.SuppressAddress in sherpa api.
|
||||||
|
|
||||||
|
// TLSRPTSuppressAddress is a reporting address for which outgoing TLS reports
|
||||||
|
// will be suppressed for a period.
|
||||||
|
type TLSRPTSuppressAddress struct {
|
||||||
|
ID int64
|
||||||
|
Inserted time.Time `bstore:"default now"`
|
||||||
|
ReportingAddress string `bstore:"unique"`
|
||||||
|
Until time.Time `bstore:"nonzero"`
|
||||||
|
Comment string
|
||||||
|
}
|
||||||
|
|
||||||
func resultDB(ctx context.Context) (rdb *bstore.DB, rerr error) {
|
func resultDB(ctx context.Context) (rdb *bstore.DB, rerr error) {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
|
@ -159,3 +171,49 @@ func RemoveResultsPolicyDomain(ctx context.Context, policyDomain dns.Domain, day
|
||||||
_, err = bstore.QueryDB[TLSResult](ctx, db).FilterNonzero(TLSResult{PolicyDomain: policyDomain.Name(), DayUTC: dayUTC}).Delete()
|
_, err = bstore.QueryDB[TLSResult](ctx, db).FilterNonzero(TLSResult{PolicyDomain: policyDomain.Name(), DayUTC: dayUTC}).Delete()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SuppressAdd adds an address to the suppress list.
|
||||||
|
func SuppressAdd(ctx context.Context, ba *TLSRPTSuppressAddress) error {
|
||||||
|
db, err := resultDB(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.Insert(ctx, ba)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuppressList returns all reporting addresses on the suppress list.
|
||||||
|
func SuppressList(ctx context.Context) ([]TLSRPTSuppressAddress, error) {
|
||||||
|
db, err := resultDB(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bstore.QueryDB[TLSRPTSuppressAddress](ctx, db).SortDesc("ID").List()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuppressRemove removes a reporting address record from the suppress list.
|
||||||
|
func SuppressRemove(ctx context.Context, id int64) error {
|
||||||
|
db, err := resultDB(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.Delete(ctx, &TLSRPTSuppressAddress{ID: id})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuppressUpdate updates the until field of a reporting address record.
|
||||||
|
func SuppressUpdate(ctx context.Context, id int64, until time.Time) error {
|
||||||
|
db, err := resultDB(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ba := TLSRPTSuppressAddress{ID: id}
|
||||||
|
err = db.Get(ctx, &ba)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ba.Until = until
|
||||||
|
return db.Update(ctx, &ba)
|
||||||
|
}
|
||||||
|
|
|
@ -441,6 +441,19 @@ Period: %s - %s UTC
|
||||||
msgSize := int64(len(msgPrefix)) + msgInfo.Size()
|
msgSize := int64(len(msgPrefix)) + msgInfo.Size()
|
||||||
|
|
||||||
for _, rcpt := range recipients {
|
for _, rcpt := range recipients {
|
||||||
|
// If recipient is on suppression list, we won't queue the reporting message.
|
||||||
|
q := bstore.QueryDB[tlsrptdb.TLSRPTSuppressAddress](ctx, db)
|
||||||
|
q.FilterNonzero(tlsrptdb.TLSRPTSuppressAddress{ReportingAddress: rcpt.Address.Path().String()})
|
||||||
|
q.FilterGreater("Until", time.Now())
|
||||||
|
exists, err := q.Exists()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("querying suppress list: %v", err)
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
log.Info("suppressing outgoing tls report", mlog.Field("reportingaddress", rcpt.Address))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
qm := queue.MakeMsg(mox.Conf.Static.Postmaster.Account, from.Path(), rcpt.Address.Path(), has8bit, smtputf8, msgSize, messageID, []byte(msgPrefix), nil)
|
qm := queue.MakeMsg(mox.Conf.Static.Postmaster.Account, from.Path(), rcpt.Address.Path(), has8bit, smtputf8, msgSize, messageID, []byte(msgPrefix), nil)
|
||||||
// Don't try as long as regular deliveries, and stop before we would send the
|
// Don't try as long as regular deliveries, and stop before we would send the
|
||||||
// delayed DSN. Though we also won't send that due to IsTLSReport.
|
// delayed DSN. Though we also won't send that due to IsTLSReport.
|
||||||
|
@ -451,7 +464,7 @@ Period: %s - %s UTC
|
||||||
no := false
|
no := false
|
||||||
qm.RequireTLS = &no
|
qm.RequireTLS = &no
|
||||||
|
|
||||||
err := queueAdd(ctx, log, &qm, msgf)
|
err = queueAdd(ctx, log, &qm, msgf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tempError = true
|
tempError = true
|
||||||
log.Errorx("queueing message with tls report", err)
|
log.Errorx("queueing message with tls report", err)
|
||||||
|
|
|
@ -381,4 +381,14 @@ func TestSendReports(t *testing.T) {
|
||||||
"tls-reports3@mailhost.sender.example": report2,
|
"tls-reports3@mailhost.sender.example": report2,
|
||||||
}
|
}
|
||||||
test(tlsResults, expReports)
|
test(tlsResults, expReports)
|
||||||
|
|
||||||
|
db.Insert(ctxbg,
|
||||||
|
&tlsrptdb.TLSRPTSuppressAddress{ReportingAddress: "tls-reports@sender.example", Until: time.Now().Add(-time.Minute)}, // Expired, so ignored.
|
||||||
|
&tlsrptdb.TLSRPTSuppressAddress{ReportingAddress: "tls-reports1@mailhost.sender.example", Until: time.Now().Add(time.Minute)}, // Still valid.
|
||||||
|
&tlsrptdb.TLSRPTSuppressAddress{ReportingAddress: "tls-reports3@mailhost.sender.example", Until: time.Now().Add(31 * 24 * time.Hour)}, // Still valid.
|
||||||
|
)
|
||||||
|
test(tlsResults, map[string]tlsrpt.Report{
|
||||||
|
"tls-reports@sender.example": report1,
|
||||||
|
"tls-reports2@mailhost.sender.example": report2,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2031,6 +2031,36 @@ func (Admin) DMARCRemoveEvaluations(ctx context.Context, domain string) {
|
||||||
xcheckf(ctx, err, "removing evaluations for domain")
|
xcheckf(ctx, err, "removing evaluations for domain")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DMARCSuppressAdd adds a reporting address to the suppress list. Outgoing
|
||||||
|
// reports will be suppressed for a period.
|
||||||
|
func (Admin) DMARCSuppressAdd(ctx context.Context, reportingAddress string, until time.Time, comment string) {
|
||||||
|
addr, err := smtp.ParseAddress(reportingAddress)
|
||||||
|
xcheckuserf(ctx, err, "parsing reporting address")
|
||||||
|
|
||||||
|
ba := dmarcdb.SuppressAddress{ReportingAddress: addr.String(), Until: until, Comment: comment}
|
||||||
|
err = dmarcdb.SuppressAdd(ctx, &ba)
|
||||||
|
xcheckf(ctx, err, "adding address to suppresslist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DMARCSuppressList returns all reporting addresses on the suppress list.
|
||||||
|
func (Admin) DMARCSuppressList(ctx context.Context) []dmarcdb.SuppressAddress {
|
||||||
|
l, err := dmarcdb.SuppressList(ctx)
|
||||||
|
xcheckf(ctx, err, "listing reporting addresses in suppresslist")
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// DMARCSuppressRemove removes a reporting address record from the suppress list.
|
||||||
|
func (Admin) DMARCSuppressRemove(ctx context.Context, id int64) {
|
||||||
|
err := dmarcdb.SuppressRemove(ctx, id)
|
||||||
|
xcheckf(ctx, err, "removing reporting address from suppresslist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DMARCSuppressExtend updates the until field of a suppressed reporting address record.
|
||||||
|
func (Admin) DMARCSuppressExtend(ctx context.Context, id int64, until time.Time) {
|
||||||
|
err := dmarcdb.SuppressUpdate(ctx, id, until)
|
||||||
|
xcheckf(ctx, err, "updating reporting address in suppresslist")
|
||||||
|
}
|
||||||
|
|
||||||
// TLSRPTResults returns all TLSRPT results in the database.
|
// TLSRPTResults returns all TLSRPT results in the database.
|
||||||
func (Admin) TLSRPTResults(ctx context.Context) []tlsrptdb.TLSResult {
|
func (Admin) TLSRPTResults(ctx context.Context) []tlsrptdb.TLSResult {
|
||||||
results, err := tlsrptdb.Results(ctx)
|
results, err := tlsrptdb.Results(ctx)
|
||||||
|
@ -2078,3 +2108,33 @@ func (Admin) TLSRPTRemoveResults(ctx context.Context, domain string, day string)
|
||||||
err = tlsrptdb.RemoveResultsPolicyDomain(ctx, dom, day)
|
err = tlsrptdb.RemoveResultsPolicyDomain(ctx, dom, day)
|
||||||
xcheckf(ctx, err, "removing tls results")
|
xcheckf(ctx, err, "removing tls results")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TLSRPTSuppressAdd adds a reporting address to the suppress list. Outgoing
|
||||||
|
// reports will be suppressed for a period.
|
||||||
|
func (Admin) TLSRPTSuppressAdd(ctx context.Context, reportingAddress string, until time.Time, comment string) {
|
||||||
|
addr, err := smtp.ParseAddress(reportingAddress)
|
||||||
|
xcheckuserf(ctx, err, "parsing reporting address")
|
||||||
|
|
||||||
|
ba := tlsrptdb.TLSRPTSuppressAddress{ReportingAddress: addr.String(), Until: until, Comment: comment}
|
||||||
|
err = tlsrptdb.SuppressAdd(ctx, &ba)
|
||||||
|
xcheckf(ctx, err, "adding address to suppresslist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSRPTSuppressList returns all reporting addresses on the suppress list.
|
||||||
|
func (Admin) TLSRPTSuppressList(ctx context.Context) []tlsrptdb.TLSRPTSuppressAddress {
|
||||||
|
l, err := tlsrptdb.SuppressList(ctx)
|
||||||
|
xcheckf(ctx, err, "listing reporting addresses in suppresslist")
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSRPTSuppressRemove removes a reporting address record from the suppress list.
|
||||||
|
func (Admin) TLSRPTSuppressRemove(ctx context.Context, id int64) {
|
||||||
|
err := tlsrptdb.SuppressRemove(ctx, id)
|
||||||
|
xcheckf(ctx, err, "removing reporting address from suppresslist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TLSRPTSuppressExtend updates the until field of a suppressed reporting address record.
|
||||||
|
func (Admin) TLSRPTSuppressExtend(ctx context.Context, id int64, until time.Time) {
|
||||||
|
err := tlsrptdb.SuppressUpdate(ctx, id, until)
|
||||||
|
xcheckf(ctx, err, "updating reporting address in suppresslist")
|
||||||
|
}
|
||||||
|
|
|
@ -1023,7 +1023,7 @@ const dmarcIndex = async () => {
|
||||||
dom._kids(page,
|
dom._kids(page,
|
||||||
crumbs(
|
crumbs(
|
||||||
crumblink('Mox Admin', '#'),
|
crumblink('Mox Admin', '#'),
|
||||||
'DMARC reports and evaluations',
|
'DMARC',
|
||||||
),
|
),
|
||||||
dom.ul(
|
dom.ul(
|
||||||
dom.li(
|
dom.li(
|
||||||
|
@ -1085,7 +1085,10 @@ const renderDMARCSummaries = (summaries) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const dmarcEvaluations = async () => {
|
const dmarcEvaluations = async () => {
|
||||||
const evalStats = await api.DMARCEvaluationStats()
|
const [evalStats, suppressAddresses] = await Promise.all([
|
||||||
|
api.DMARCEvaluationStats(),
|
||||||
|
api.DMARCSuppressList(),
|
||||||
|
])
|
||||||
|
|
||||||
const isEmpty = (o) => {
|
const isEmpty = (o) => {
|
||||||
for (const e in o) {
|
for (const e in o) {
|
||||||
|
@ -1094,6 +1097,9 @@ const dmarcEvaluations = async () => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let fieldset, reportingAddress, until, comment
|
||||||
|
const nextmonth = new Date(new Date().getTime()+31*24*3600*1000)
|
||||||
|
|
||||||
const page = document.getElementById('page')
|
const page = document.getElementById('page')
|
||||||
dom._kids(page,
|
dom._kids(page,
|
||||||
crumbs(
|
crumbs(
|
||||||
|
@ -1121,6 +1127,101 @@ const dmarcEvaluations = async () => {
|
||||||
isEmpty(evalStats) ? dom.tr(dom.td(attr({colspan: '3'}), 'No evaluations.')) : [],
|
isEmpty(evalStats) ? dom.tr(dom.td(attr({colspan: '3'}), 'No evaluations.')) : [],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
dom.br(),
|
||||||
|
dom.br(),
|
||||||
|
dom.h2('Suppressed reporting addresses'),
|
||||||
|
dom.p('In practice, sending a DMARC report to a reporting address can cause DSN to be sent back. Such addresses can be added to a supression list for a period, to reduce noise in the postmaster mailbox.'),
|
||||||
|
dom.form(
|
||||||
|
async function submit(e) {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
try {
|
||||||
|
fieldset.disabled = true
|
||||||
|
await api.DMARCSuppressAdd(reportingAddress.value, new Date(until.value), comment.value)
|
||||||
|
} catch (err) {
|
||||||
|
console.log({err})
|
||||||
|
window.alert('Error: ' + err.message)
|
||||||
|
return
|
||||||
|
} finally {
|
||||||
|
fieldset.disabled = false
|
||||||
|
}
|
||||||
|
window.location.reload() // todo: add the address to the list, or only reload the list
|
||||||
|
},
|
||||||
|
fieldset=dom.fieldset(
|
||||||
|
dom.label(
|
||||||
|
style({display: 'inline-block'}),
|
||||||
|
'Reporting address',
|
||||||
|
dom.br(),
|
||||||
|
reportingAddress=dom.input(attr({required: ''})),
|
||||||
|
),
|
||||||
|
' ',
|
||||||
|
dom.label(
|
||||||
|
style({display: 'inline-block'}),
|
||||||
|
'Until',
|
||||||
|
dom.br(),
|
||||||
|
until=dom.input(attr({type: 'date', required: '', value: nextmonth.getFullYear()+'-'+(1+nextmonth.getMonth())+'-'+nextmonth.getDate()})),
|
||||||
|
),
|
||||||
|
' ',
|
||||||
|
dom.label(
|
||||||
|
style({display: 'inline-block'}),
|
||||||
|
dom.span('Comment (optional)'),
|
||||||
|
dom.br(),
|
||||||
|
comment=dom.input(),
|
||||||
|
),
|
||||||
|
' ',
|
||||||
|
dom.button('Add', attr({title: 'Outgoing reports to this reporting address will be suppressed until the end time.'})),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dom.br(),
|
||||||
|
dom('table.hover',
|
||||||
|
dom.thead(
|
||||||
|
dom.tr(
|
||||||
|
dom.th('Reporting address'),
|
||||||
|
dom.th('Until'),
|
||||||
|
dom.th('Comment'),
|
||||||
|
dom.th('Action'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dom.tbody(
|
||||||
|
(suppressAddresses || []).length === 0 ? dom.tr(dom.td(attr({colspan: '4'}), 'No suppressed reporting addresses.')) : [],
|
||||||
|
(suppressAddresses || []).map(ba =>
|
||||||
|
dom.tr(
|
||||||
|
dom.td(ba.ReportingAddress),
|
||||||
|
dom.td(ba.Until),
|
||||||
|
dom.td(ba.Comment),
|
||||||
|
dom.td(
|
||||||
|
dom.button('Remove', attr({type: 'button'}), async function click(e) {
|
||||||
|
try {
|
||||||
|
e.target.disabled = true
|
||||||
|
await api.DMARCSuppressRemove(ba.ID)
|
||||||
|
} catch (err) {
|
||||||
|
console.log({err})
|
||||||
|
window.alert('Error: ' + err.message)
|
||||||
|
return
|
||||||
|
} finally {
|
||||||
|
e.target.disabled = false
|
||||||
|
}
|
||||||
|
window.location.reload() // todo: only reload the list
|
||||||
|
}),
|
||||||
|
' ',
|
||||||
|
dom.button('Extend for 1 month', attr({type: 'button'}), async function click(e) {
|
||||||
|
try {
|
||||||
|
e.target.disabled = true
|
||||||
|
await api.DMARCSuppressExtend(ba.ID, new Date(new Date().getTime() + 31*24*3600*1000))
|
||||||
|
} catch (err) {
|
||||||
|
console.log({err})
|
||||||
|
window.alert('Error: ' + err.message)
|
||||||
|
return
|
||||||
|
} finally {
|
||||||
|
e.target.disabled = false
|
||||||
|
}
|
||||||
|
window.location.reload() // todo: only reload the list
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1479,10 +1580,16 @@ const tlsrptIndex = async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const tlsrptResults = async () => {
|
const tlsrptResults = async () => {
|
||||||
const results = await api.TLSRPTResults()
|
const [results, suppressAddresses] = await Promise.all([
|
||||||
|
api.TLSRPTResults(),
|
||||||
|
api.TLSRPTSuppressList(),
|
||||||
|
])
|
||||||
|
|
||||||
// todo: add a view where results are grouped by policy domain+dayutc. now each recipient domain gets a row.
|
// todo: add a view where results are grouped by policy domain+dayutc. now each recipient domain gets a row.
|
||||||
|
|
||||||
|
let fieldset, reportingAddress, until, comment
|
||||||
|
const nextmonth = new Date(new Date().getTime()+31*24*3600*1000)
|
||||||
|
|
||||||
const page = document.getElementById('page')
|
const page = document.getElementById('page')
|
||||||
dom._kids(page,
|
dom._kids(page,
|
||||||
crumbs(
|
crumbs(
|
||||||
|
@ -1545,6 +1652,101 @@ const tlsrptResults = async () => {
|
||||||
results.length === 0 ? dom.tr(dom.td(attr({colspan: '9'}), 'No results.')) : [],
|
results.length === 0 ? dom.tr(dom.td(attr({colspan: '9'}), 'No results.')) : [],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
dom.br(),
|
||||||
|
dom.br(),
|
||||||
|
dom.h2('Suppressed reporting addresses'),
|
||||||
|
dom.p('In practice, sending a TLS report to a reporting address can cause DSN to be sent back. Such addresses can be added to a suppress list for a period, to reduce noise in the postmaster mailbox.'),
|
||||||
|
dom.form(
|
||||||
|
async function submit(e) {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
try {
|
||||||
|
fieldset.disabled = true
|
||||||
|
await api.TLSRPTSuppressAdd(reportingAddress.value, new Date(until.value), comment.value)
|
||||||
|
} catch (err) {
|
||||||
|
console.log({err})
|
||||||
|
window.alert('Error: ' + err.message)
|
||||||
|
return
|
||||||
|
} finally {
|
||||||
|
fieldset.disabled = false
|
||||||
|
}
|
||||||
|
window.location.reload() // todo: add the address to the list, or only reload the list
|
||||||
|
},
|
||||||
|
fieldset=dom.fieldset(
|
||||||
|
dom.label(
|
||||||
|
style({display: 'inline-block'}),
|
||||||
|
'Reporting address',
|
||||||
|
dom.br(),
|
||||||
|
reportingAddress=dom.input(attr({required: ''})),
|
||||||
|
),
|
||||||
|
' ',
|
||||||
|
dom.label(
|
||||||
|
style({display: 'inline-block'}),
|
||||||
|
'Until',
|
||||||
|
dom.br(),
|
||||||
|
until=dom.input(attr({type: 'date', required: '', value: nextmonth.getFullYear()+'-'+(1+nextmonth.getMonth())+'-'+nextmonth.getDate()})),
|
||||||
|
),
|
||||||
|
' ',
|
||||||
|
dom.label(
|
||||||
|
style({display: 'inline-block'}),
|
||||||
|
dom.span('Comment (optional)'),
|
||||||
|
dom.br(),
|
||||||
|
comment=dom.input(),
|
||||||
|
),
|
||||||
|
' ',
|
||||||
|
dom.button('Add', attr({title: 'Outgoing reports to this reporting address will be suppressed until the end time.'})),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dom.br(),
|
||||||
|
dom('table.hover',
|
||||||
|
dom.thead(
|
||||||
|
dom.tr(
|
||||||
|
dom.th('Reporting address'),
|
||||||
|
dom.th('Until'),
|
||||||
|
dom.th('Comment'),
|
||||||
|
dom.th('Action'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dom.tbody(
|
||||||
|
(suppressAddresses || []).length === 0 ? dom.tr(dom.td(attr({colspan: '4'}), 'No suppressed reporting addresses.')) : [],
|
||||||
|
(suppressAddresses || []).map(ba =>
|
||||||
|
dom.tr(
|
||||||
|
dom.td(ba.ReportingAddress),
|
||||||
|
dom.td(ba.Until),
|
||||||
|
dom.td(ba.Comment),
|
||||||
|
dom.td(
|
||||||
|
dom.button('Remove', attr({type: 'button'}), async function click(e) {
|
||||||
|
try {
|
||||||
|
e.target.disabled = true
|
||||||
|
await api.TLSRPTSuppressRemove(ba.ID)
|
||||||
|
} catch (err) {
|
||||||
|
console.log({err})
|
||||||
|
window.alert('Error: ' + err.message)
|
||||||
|
return
|
||||||
|
} finally {
|
||||||
|
e.target.disabled = false
|
||||||
|
}
|
||||||
|
window.location.reload() // todo: only reload the list
|
||||||
|
}),
|
||||||
|
' ',
|
||||||
|
dom.button('Extend for 1 month', attr({type: 'button'}), async function click(e) {
|
||||||
|
try {
|
||||||
|
e.target.disabled = true
|
||||||
|
await api.TLSRPTSuppressExtend(ba.ID, new Date(new Date().getTime() + 31*24*3600*1000))
|
||||||
|
} catch (err) {
|
||||||
|
console.log({err})
|
||||||
|
window.alert('Error: ' + err.message)
|
||||||
|
return
|
||||||
|
} finally {
|
||||||
|
e.target.disabled = false
|
||||||
|
}
|
||||||
|
window.location.reload() // todo: only reload the list
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -828,6 +828,77 @@
|
||||||
],
|
],
|
||||||
"Returns": []
|
"Returns": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Name": "DMARCSuppressAdd",
|
||||||
|
"Docs": "DMARCSuppressAdd adds a reporting address to the suppress list. Outgoing\nreports will be suppressed for a period.",
|
||||||
|
"Params": [
|
||||||
|
{
|
||||||
|
"Name": "reportingAddress",
|
||||||
|
"Typewords": [
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "until",
|
||||||
|
"Typewords": [
|
||||||
|
"timestamp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "comment",
|
||||||
|
"Typewords": [
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Returns": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "DMARCSuppressList",
|
||||||
|
"Docs": "DMARCSuppressList returns all reporting addresses on the suppress list.",
|
||||||
|
"Params": [],
|
||||||
|
"Returns": [
|
||||||
|
{
|
||||||
|
"Name": "r0",
|
||||||
|
"Typewords": [
|
||||||
|
"[]",
|
||||||
|
"SuppressAddress"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "DMARCSuppressRemove",
|
||||||
|
"Docs": "DMARCSuppressRemove removes a reporting address record from the suppress list.",
|
||||||
|
"Params": [
|
||||||
|
{
|
||||||
|
"Name": "id",
|
||||||
|
"Typewords": [
|
||||||
|
"int64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Returns": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "DMARCSuppressExtend",
|
||||||
|
"Docs": "DMARCSuppressExtend updates the until field of a suppressed reporting address record.",
|
||||||
|
"Params": [
|
||||||
|
{
|
||||||
|
"Name": "id",
|
||||||
|
"Typewords": [
|
||||||
|
"int64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "until",
|
||||||
|
"Typewords": [
|
||||||
|
"timestamp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Returns": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Name": "TLSRPTResults",
|
"Name": "TLSRPTResults",
|
||||||
"Docs": "TLSRPTResults returns all TLSRPT results in the database.",
|
"Docs": "TLSRPTResults returns all TLSRPT results in the database.",
|
||||||
|
@ -920,6 +991,77 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Returns": []
|
"Returns": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "TLSRPTSuppressAdd",
|
||||||
|
"Docs": "TLSRPTSuppressAdd adds a reporting address to the suppress list. Outgoing\nreports will be suppressed for a period.",
|
||||||
|
"Params": [
|
||||||
|
{
|
||||||
|
"Name": "reportingAddress",
|
||||||
|
"Typewords": [
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "until",
|
||||||
|
"Typewords": [
|
||||||
|
"timestamp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "comment",
|
||||||
|
"Typewords": [
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Returns": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "TLSRPTSuppressList",
|
||||||
|
"Docs": "TLSRPTSuppressList returns all reporting addresses on the suppress list.",
|
||||||
|
"Params": [],
|
||||||
|
"Returns": [
|
||||||
|
{
|
||||||
|
"Name": "r0",
|
||||||
|
"Typewords": [
|
||||||
|
"[]",
|
||||||
|
"TLSRPTSuppressAddress"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "TLSRPTSuppressRemove",
|
||||||
|
"Docs": "TLSRPTSuppressRemove removes a reporting address record from the suppress list.",
|
||||||
|
"Params": [
|
||||||
|
{
|
||||||
|
"Name": "id",
|
||||||
|
"Typewords": [
|
||||||
|
"int64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Returns": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "TLSRPTSuppressExtend",
|
||||||
|
"Docs": "TLSRPTSuppressExtend updates the until field of a suppressed reporting address record.",
|
||||||
|
"Params": [
|
||||||
|
{
|
||||||
|
"Name": "id",
|
||||||
|
"Typewords": [
|
||||||
|
"int64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "until",
|
||||||
|
"Typewords": [
|
||||||
|
"timestamp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Returns": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Sections": [],
|
"Sections": [],
|
||||||
|
@ -3808,6 +3950,47 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Name": "SuppressAddress",
|
||||||
|
"Docs": "SuppressAddress is a reporting address for which outgoing DMARC reports\nwill be suppressed for a period.",
|
||||||
|
"Fields": [
|
||||||
|
{
|
||||||
|
"Name": "ID",
|
||||||
|
"Docs": "",
|
||||||
|
"Typewords": [
|
||||||
|
"int64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Inserted",
|
||||||
|
"Docs": "",
|
||||||
|
"Typewords": [
|
||||||
|
"timestamp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "ReportingAddress",
|
||||||
|
"Docs": "",
|
||||||
|
"Typewords": [
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Until",
|
||||||
|
"Docs": "",
|
||||||
|
"Typewords": [
|
||||||
|
"timestamp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Comment",
|
||||||
|
"Docs": "",
|
||||||
|
"Typewords": [
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Name": "TLSResult",
|
"Name": "TLSResult",
|
||||||
"Docs": "TLSResult is stored in the database to track TLS results per policy domain, day\nand recipient domain. These records will be included in TLS reports.",
|
"Docs": "TLSResult is stored in the database to track TLS results per policy domain, day\nand recipient domain. These records will be included in TLS reports.",
|
||||||
|
@ -3877,6 +4060,47 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "TLSRPTSuppressAddress",
|
||||||
|
"Docs": "TLSRPTSuppressAddress is a reporting address for which outgoing TLS reports\nwill be suppressed for a period.",
|
||||||
|
"Fields": [
|
||||||
|
{
|
||||||
|
"Name": "ID",
|
||||||
|
"Docs": "",
|
||||||
|
"Typewords": [
|
||||||
|
"int64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Inserted",
|
||||||
|
"Docs": "",
|
||||||
|
"Typewords": [
|
||||||
|
"timestamp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "ReportingAddress",
|
||||||
|
"Docs": "",
|
||||||
|
"Typewords": [
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Until",
|
||||||
|
"Docs": "",
|
||||||
|
"Typewords": [
|
||||||
|
"timestamp"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "Comment",
|
||||||
|
"Docs": "",
|
||||||
|
"Typewords": [
|
||||||
|
"string"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Ints": [],
|
"Ints": [],
|
||||||
|
|
Loading…
Reference in a new issue