Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions service/email_domain_dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type emailDomainDNSCheck struct {
MXHost string // first MX host, lowercase, trailing dot trimmed
ARecord bool // at least one A/AAAA record resolves
PrivateARecord bool // resolved A points to loopback/private/link-local (parked placeholder)
WebsiteReachable bool // an HTTP(S) server answered on port 80/443
WebsiteReachable bool // an HTTP(S) server returned a non-error status on port 80/443
MajorProviderMX bool // MX host belongs to a mainstream mail provider (gmail/outlook/qq/...)
DisposableMX bool // MX host matches known disposable/temp-mail infrastructure
}
Expand Down Expand Up @@ -265,8 +265,13 @@ func safeEmailDomainDialContext(ctx context.Context, network, addr string) (net.
}

// emailDomainWebsiteReachable returns true if an HTTP(S) server answers on the
// domain (any status code counts — a real site exists). TLS failure falls back
// to plain HTTP. Connections are restricted to public IPs (SSRF hardening).
// domain with a non-error (2xx/3xx) status. 4xx/5xx responses mean the root
// domain is unavailable for registration. TLS failure falls back to plain HTTP.
// Connections are restricted to public IPs (SSRF hardening).
func emailDomainWebsiteStatusOK(statusCode int) bool {
return statusCode >= http.StatusOK && statusCode < http.StatusBadRequest
}

func emailDomainWebsiteReachable(domain string) bool {
for _, scheme := range []string{"https", "http"} {
ctx, cancel := context.WithTimeout(context.Background(), 2500*time.Millisecond)
Expand All @@ -280,7 +285,7 @@ func emailDomainWebsiteReachable(domain string) bool {
cancel()
if err == nil {
resp.Body.Close()
return true
return emailDomainWebsiteStatusOK(resp.StatusCode)
}
}
return false
Expand Down
9 changes: 9 additions & 0 deletions service/email_domain_dns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,12 @@ func TestSafeEmailDomainDialContextRefusesPrivateTargets(t *testing.T) {
_, err = safeEmailDomainDialContext(context.Background(), "tcp", "169.254.169.254:80")
require.Error(t, err)
}

func TestEmailDomainWebsiteStatusRequiresSuccessfulResponse(t *testing.T) {
for _, status := range []int{200, 204, 301, 302, 399} {
require.True(t, emailDomainWebsiteStatusOK(status), status)
}
for _, status := range []int{400, 401, 403, 404, 500, 502, 503} {
require.False(t, emailDomainWebsiteStatusOK(status), status)
}
}
2 changes: 1 addition & 1 deletion service/registration_security.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func EvaluateRegistrationEmail(email string, cfg system_setting.RegistrationSecu
if cfg.RejectEmailDomainWithoutMX && !dnsCheck.MXRecord {
return RegistrationEmailDecision{}, ErrRegistrationDomainUnavailable
}
if cfg.RejectEmailDomainWithoutWebsite && !dnsCheck.WebsiteReachable && !dnsCheck.MajorProviderMX {
if cfg.RejectEmailDomainWithoutWebsite && !dnsCheck.WebsiteReachable {
return RegistrationEmailDecision{}, ErrRegistrationDomainUnavailable
}
}
Expand Down
4 changes: 2 additions & 2 deletions service/registration_security_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,11 @@ func TestRegistrationSecurityDNSMissingWebsitePolicy(t *testing.T) {
_, err = EvaluateRegistrationEmail("user@withweb.example", cfg, nil)
require.NoError(t, err)

// major-provider MX exempts from the website requirement (email-only domain)
// A major-provider MX does not exempt a domain whose root website is unavailable.
restore = stubDNSChecker(emailDomainDNSCheck{MXRecord: true, MXHost: "alt1.gmail-smtp-in.l.google.com", MajorProviderMX: true})
defer restore()
_, err = EvaluateRegistrationEmail("user@corp.example", cfg, nil)
require.NoError(t, err)
require.ErrorIs(t, err, ErrRegistrationDomainUnavailable)
}

func TestRegistrationSecurityDNSDisabledByDefaultInPolicy(t *testing.T) {
Expand Down