-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdmarc_feedback.go
More file actions
89 lines (78 loc) · 2.97 KB
/
Copy pathdmarc_feedback.go
File metadata and controls
89 lines (78 loc) · 2.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package store
import (
"time"
)
// SaveDMARCFeedback records a DMARC authentication result for aggregate reporting
func (db *DB) SaveDMARCFeedback(domain, sourceIP, envelopeFromDomain, dkimResult, spfResult, disposition string) error {
_, err := db.Exec(`
INSERT INTO dmarc_feedback (domain, source_ip, envelope_from_domain, dkim_result, spf_result, disposition, received_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
`, domain, sourceIP, envelopeFromDomain, dkimResult, spfResult, disposition)
return err
}
// GetDMARCFeedbackForReport retrieves aggregated DMARC feedback for a domain within a time range
// Returns: map[sourceIP]stats for building DMARC aggregate report
func (db *DB) GetDMARCFeedbackForReport(domain string, startTime, endTime time.Time) (map[string]int, error) {
rows, err := db.Query(`
SELECT source_ip, COUNT(*) as count
FROM dmarc_feedback
WHERE domain = ? AND received_at >= datetime(?) AND received_at < datetime(?)
GROUP BY source_ip
ORDER BY count DESC
`, domain, startTime.Format(time.RFC3339), endTime.Format(time.RFC3339))
if err != nil {
return nil, err
}
defer rows.Close()
results := make(map[string]int)
for rows.Next() {
var sourceIP string
var count int
if err := rows.Scan(&sourceIP, &count); err != nil {
return nil, err
}
results[sourceIP] = count
}
return results, rows.Err()
}
// CleanOldDMARCFeedback removes records older than retentionDays days
func (db *DB) CleanOldDMARCFeedback(retentionDays int) error {
cutoff := time.Now().AddDate(0, 0, -retentionDays)
_, err := db.Exec(`
DELETE FROM dmarc_feedback WHERE received_at < datetime(?)
`, cutoff.Format(time.RFC3339))
return err
}
// MarkDMARCFeedbackAsSent marks all unsent records for a domain in a time range as sent
func (db *DB) MarkDMARCFeedbackAsSent(domain string, startTime, endTime time.Time) error {
_, err := db.Exec(`
UPDATE dmarc_feedback
SET sent_at = datetime('now')
WHERE domain = ? AND received_at >= datetime(?) AND received_at < datetime(?) AND sent_at IS NULL
`, domain, startTime.Format(time.RFC3339), endTime.Format(time.RFC3339))
return err
}
// GetLastDMARCReportTime returns when the last report was sent for a domain (or epoch if never sent)
func (db *DB) GetLastDMARCReportTime(domain string) (time.Time, error) {
var lastSent *time.Time
err := db.QueryRow(`
SELECT last_sent_at FROM dmarc_report_log WHERE domain = ?
`, domain).Scan(&lastSent)
if err != nil {
// Domain not in log yet, return epoch time (Jan 1, 1970)
return time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), nil
}
if lastSent == nil {
return time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), nil
}
return *lastSent, nil
}
// UpdateLastDMARCReportTime records when a report was successfully sent for a domain
func (db *DB) UpdateLastDMARCReportTime(domain string) error {
_, err := db.Exec(`
INSERT INTO dmarc_report_log (domain, last_sent_at)
VALUES (?, datetime('now'))
ON CONFLICT(domain) DO UPDATE SET last_sent_at = datetime('now')
`, domain)
return err
}