-
Notifications
You must be signed in to change notification settings - Fork 6
/
domain.go
130 lines (118 loc) · 4.74 KB
/
domain.go
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package models
import (
"fmt"
"log"
"time"
"github.com/EFForg/starttls-backend/checker"
)
// Domain stores the preload state of a single domain.
type Domain struct {
Name string `json:"domain"` // Domain that is preloaded
Email string `json:"-"` // Contact e-mail for Domain
MXs []string `json:"mxs"` // MXs that are valid for this domain
MTASTS bool `json:"mta_sts"`
State DomainState `json:"state"`
LastUpdated time.Time `json:"last_updated"`
TestingStart time.Time `json:"-"`
QueueWeeks int `json:"queue_weeks"`
}
// domainStore is a simple interface for fetching and adding domain objects.
type domainStore interface {
PutDomain(Domain) error
GetDomain(string) (Domain, error)
GetDomains(DomainState) ([]Domain, error)
}
// DomainState represents the state of a single domain.
type DomainState string
// Possible values for DomainState
const (
StateUnknown = "unknown" // Domain was never submitted, so we don't know.
StateUnvalidated = "unvalidated" // E-mail token for this domain is unverified
StateTesting = "queued" // Queued for addition at next addition date.
StateFailed = "failed" // Requested to be queued, but failed verification.
StateEnforce = "added" // On the list.
)
type policyList interface {
HasDomain(string) bool
}
// IsQueueable returns true if a domain can be submitted for validation and
// queueing to the STARTTLS Everywhere Policy List.
// A successful scan should already have been submitted for this domain,
// and it should not already be on the policy list.
// Returns (queuability, error message, and most recent scan)
func (d *Domain) IsQueueable(db scanStore, list policyList) (bool, string, Scan) {
scan, err := db.GetLatestScan(d.Name)
if err != nil {
return false, "We haven't scanned this domain yet. " +
"Please use the STARTTLS checker to scan your domain's " +
"STARTTLS configuration so we can validate your submission", scan
}
if scan.Data.Status != 0 {
return false, "Domain hasn't passed our STARTTLS security checks", scan
}
if list.HasDomain(d.Name) {
return false, "Domain is already on the policy list!", scan
}
// Domains without submitted MTA-STS support must match provided mx patterns.
if !d.MTASTS {
for _, hostname := range scan.Data.PreferredHostnames {
if !checker.PolicyMatches(hostname, d.MXs) {
return false, fmt.Sprintf("Hostnames %v do not match policy %v", scan.Data.PreferredHostnames, d.MXs), scan
}
}
} else if !scan.SupportsMTASTS() {
return false, "Domain does not correctly implement MTA-STS.", scan
}
return true, "", scan
}
// PopulateFromScan updates a Domain's fields based on a scan of that domain.
func (d *Domain) PopulateFromScan(scan Scan) {
// We should only trust MTA-STS info from a successful MTA-STS check.
if d.MTASTS && scan.SupportsMTASTS() {
// If the domain's MXs are missing, we can take them from the scan's
// PreferredHostnames, which must be a subset of those listed in the
// MTA-STS policy file.
if len(d.MXs) == 0 {
d.MXs = scan.Data.MTASTSResult.MXs
}
}
}
// InitializeWithToken adds this domain to the given DomainStore and initializes a validation token
// for the addition. The newly generated Token is returned.
func (d *Domain) InitializeWithToken(store domainStore, tokens tokenStore) (string, error) {
if err := store.PutDomain(*d); err != nil {
return "", err
}
token, err := tokens.PutToken(d.Name)
if err != nil {
return "", err
}
return token.Token, nil
}
// PolicyListCheck checks the policy list status of this particular domain.
func (d *Domain) PolicyListCheck(store domainStore, list policyList) *checker.Result {
result := checker.Result{Name: checker.PolicyList}
if list.HasDomain(d.Name) {
return result.Success()
}
domainData, err := store.GetDomain(d.Name)
if err != nil {
return result.Failure("Domain %s is not on the policy list.", d.Name)
}
if domainData.State == StateEnforce {
log.Println("Warning: Domain was StateEnforce in DB but was not found on the policy list.")
return result.Success()
} else if domainData.State == StateTesting {
return result.Warning("Domain %s is queued to be added to the policy list.", d.Name)
} else if domainData.State == StateUnvalidated {
return result.Warning("The policy addition request for %s is waiting on email validation", d.Name)
}
return result.Failure("Domain %s is not on the policy list.", d.Name)
}
// AsyncPolicyListCheck performs PolicyListCheck asynchronously.
// domainStore and policyList should be safe for concurrent use.
func (d Domain) AsyncPolicyListCheck(store domainStore, list policyList) <-chan checker.Result {
result := make(chan checker.Result)
go func() { result <- *d.PolicyListCheck(store, list) }()
return result
}