326 lines
10 KiB
Go
326 lines
10 KiB
Go
package checker
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// hasIssue reports whether the issue list contains an entry with the
|
|
// given code. Used by the table-driven tests below.
|
|
func hasIssue(issues []Issue, code string) bool {
|
|
for _, is := range issues {
|
|
if is.Code == code {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func issueByCode(issues []Issue, code string) *Issue {
|
|
for i := range issues {
|
|
if issues[i].Code == code {
|
|
return &issues[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestDeriveIssues_NullMXShortCircuits(t *testing.T) {
|
|
d := &SMTPData{
|
|
Domain: "example.com",
|
|
MX: MXLookup{NullMX: true, Records: []MXRecord{{Target: "."}}},
|
|
Endpoints: []EndpointProbe{
|
|
{Target: "mx", IP: "1.2.3.4", Address: "1.2.3.4:25"}, // would normally yield issues
|
|
},
|
|
}
|
|
issues := deriveIssues(d)
|
|
if len(issues) != 1 {
|
|
t.Fatalf("null MX should short-circuit; got %d issues", len(issues))
|
|
}
|
|
if issues[0].Code != CodeNullMX {
|
|
t.Errorf("want CodeNullMX, got %q", issues[0].Code)
|
|
}
|
|
if issues[0].Severity != SeverityInfo {
|
|
t.Errorf("null MX is informational, got %q", issues[0].Severity)
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_MXLookupFailed(t *testing.T) {
|
|
d := &SMTPData{Domain: "x", MX: MXLookup{Error: "servfail"}}
|
|
issues := deriveIssues(d)
|
|
if !hasIssue(issues, CodeMXLookupFailed) {
|
|
t.Fatalf("expected mx lookup failed, got %+v", issues)
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_ImplicitMX(t *testing.T) {
|
|
d := &SMTPData{Domain: "x", MX: MXLookup{ImplicitMX: true}}
|
|
issues := deriveIssues(d)
|
|
if !hasIssue(issues, CodeImplicitMX) {
|
|
t.Fatalf("expected implicit MX issue")
|
|
}
|
|
is := issueByCode(issues, CodeImplicitMX)
|
|
if is.Severity != SeverityWarn {
|
|
t.Errorf("implicit MX should be warn, got %q", is.Severity)
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_NoMX(t *testing.T) {
|
|
d := &SMTPData{Domain: "x"}
|
|
issues := deriveIssues(d)
|
|
if !hasIssue(issues, CodeNoMX) {
|
|
t.Fatalf("expected no-mx issue")
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_MXIPLiteral(t *testing.T) {
|
|
d := &SMTPData{
|
|
Domain: "x",
|
|
MX: MXLookup{Records: []MXRecord{
|
|
{Preference: 10, Target: "192.0.2.1", IsIPLiteral: true, IPv4: []string{"192.0.2.1"}},
|
|
}},
|
|
}
|
|
issues := deriveIssues(d)
|
|
if !hasIssue(issues, CodeMXIPLiteral) {
|
|
t.Fatalf("expected ip-literal issue")
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_MXResolveFailed(t *testing.T) {
|
|
d := &SMTPData{
|
|
Domain: "x",
|
|
MX: MXLookup{Records: []MXRecord{
|
|
{Preference: 10, Target: "mx.x", ResolveError: "nxdomain"},
|
|
}},
|
|
}
|
|
issues := deriveIssues(d)
|
|
if !hasIssue(issues, CodeMXResolveFailed) {
|
|
t.Fatalf("expected resolve-failed issue")
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_MXNoAddresses(t *testing.T) {
|
|
d := &SMTPData{
|
|
Domain: "x",
|
|
MX: MXLookup{Records: []MXRecord{
|
|
{Preference: 10, Target: "mx.x"}, // no IPs, no error
|
|
}},
|
|
}
|
|
issues := deriveIssues(d)
|
|
if !hasIssue(issues, CodeNoAddresses) {
|
|
t.Fatalf("expected no-addresses issue")
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_TCPUnreachable(t *testing.T) {
|
|
d := &SMTPData{
|
|
Domain: "x",
|
|
MX: MXLookup{Records: []MXRecord{
|
|
{Target: "mx.x", IPv4: []string{"1.2.3.4"}},
|
|
}},
|
|
Endpoints: []EndpointProbe{
|
|
{Target: "mx.x", IP: "1.2.3.4", Address: "1.2.3.4:25", Error: "connection refused"},
|
|
},
|
|
}
|
|
issues := deriveIssues(d)
|
|
if !hasIssue(issues, CodeTCPUnreachable) {
|
|
t.Fatalf("expected tcp-unreachable")
|
|
}
|
|
if !hasIssue(issues, CodeAllEndpointsDown) {
|
|
t.Fatalf("expected all-endpoints-down summary")
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_BannerMissingAndInvalid(t *testing.T) {
|
|
d := &SMTPData{
|
|
Domain: "x",
|
|
MX: MXLookup{Records: []MXRecord{{Target: "mx.x", IPv4: []string{"1.2.3.4"}}}},
|
|
Endpoints: []EndpointProbe{
|
|
{Target: "mx.x", IP: "1.2.3.4", Address: "1.2.3.4:25", TCPConnected: true},
|
|
{Target: "mx.x", IP: "1.2.3.5", Address: "1.2.3.5:25", TCPConnected: true, BannerReceived: true, BannerCode: 421},
|
|
},
|
|
}
|
|
issues := deriveIssues(d)
|
|
if !hasIssue(issues, CodeBannerMissing) {
|
|
t.Errorf("expected banner-missing")
|
|
}
|
|
if !hasIssue(issues, CodeBannerInvalid) {
|
|
t.Errorf("expected banner-invalid")
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_EHLOFailedAndFallback(t *testing.T) {
|
|
d := &SMTPData{
|
|
Domain: "x",
|
|
MX: MXLookup{Records: []MXRecord{{Target: "mx.x", IPv4: []string{"1.2.3.4"}}}},
|
|
Endpoints: []EndpointProbe{
|
|
{Target: "mx.x", IP: "1.2.3.4", Address: "1.2.3.4:25", TCPConnected: true, BannerReceived: true, BannerCode: 220},
|
|
{Target: "mx.x", IP: "1.2.3.5", Address: "1.2.3.5:25", TCPConnected: true, BannerReceived: true, BannerCode: 220, EHLOReceived: true, EHLOFallbackHELO: true},
|
|
},
|
|
}
|
|
issues := deriveIssues(d)
|
|
if !hasIssue(issues, CodeEHLOFailed) {
|
|
t.Errorf("expected ehlo-failed")
|
|
}
|
|
if !hasIssue(issues, CodeEHLOFallback) {
|
|
t.Errorf("expected ehlo-fallback")
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_STARTTLSMissingAndFailed(t *testing.T) {
|
|
d := &SMTPData{
|
|
Domain: "x",
|
|
MX: MXLookup{Records: []MXRecord{{Target: "mx.x", IPv4: []string{"1.2.3.4"}}}},
|
|
Endpoints: []EndpointProbe{
|
|
{Target: "mx.x", IP: "1.2.3.4", Address: "1.2.3.4:25", TCPConnected: true, BannerReceived: true, BannerCode: 220, EHLOReceived: true}, // no STARTTLS offered
|
|
{Target: "mx.x", IP: "1.2.3.5", Address: "1.2.3.5:25", TCPConnected: true, BannerReceived: true, BannerCode: 220, EHLOReceived: true, STARTTLSOffered: true, Error: "ssl bad"}, // offered but not upgraded
|
|
},
|
|
}
|
|
issues := deriveIssues(d)
|
|
if !hasIssue(issues, CodeSTARTTLSMissing) {
|
|
t.Errorf("expected starttls missing")
|
|
}
|
|
if !hasIssue(issues, CodeSTARTTLSFailed) {
|
|
t.Errorf("expected starttls failed")
|
|
}
|
|
if !hasIssue(issues, CodeAllNoSTARTTLS) {
|
|
t.Errorf("expected summary all-no-starttls")
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_AUTHOverPlain(t *testing.T) {
|
|
d := &SMTPData{
|
|
Domain: "x",
|
|
MX: MXLookup{Records: []MXRecord{{Target: "mx.x", IPv4: []string{"1.2.3.4"}}}},
|
|
Endpoints: []EndpointProbe{
|
|
{Target: "mx.x", IP: "1.2.3.4", Address: "1.2.3.4:25", TCPConnected: true, BannerReceived: true, BannerCode: 220, EHLOReceived: true, AUTHPreTLS: []string{"PLAIN", "LOGIN"}},
|
|
},
|
|
}
|
|
issues := deriveIssues(d)
|
|
is := issueByCode(issues, CodeAUTHOverPlain)
|
|
if is == nil {
|
|
t.Fatalf("expected auth-over-plain issue")
|
|
}
|
|
if !strings.Contains(is.Message, "PLAIN") || !strings.Contains(is.Message, "LOGIN") {
|
|
t.Errorf("auth message should list mechanisms, got %q", is.Message)
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_PTRAndFCrDNS(t *testing.T) {
|
|
d := &SMTPData{
|
|
Domain: "x",
|
|
MX: MXLookup{Records: []MXRecord{{Target: "mx.x", IPv4: []string{"1.2.3.4"}}}},
|
|
Endpoints: []EndpointProbe{
|
|
{Target: "mx.x", IP: "1.2.3.4", Address: "1.2.3.4:25", TCPConnected: true, BannerReceived: true, BannerCode: 220, EHLOReceived: true, STARTTLSOffered: true, STARTTLSUpgraded: true},
|
|
{Target: "mx.x", IP: "1.2.3.5", Address: "1.2.3.5:25", TCPConnected: true, BannerReceived: true, BannerCode: 220, EHLOReceived: true, STARTTLSOffered: true, STARTTLSUpgraded: true, PTR: "wrong.example.com", FCrDNSPass: false},
|
|
},
|
|
}
|
|
issues := deriveIssues(d)
|
|
if !hasIssue(issues, CodePTRMissing) {
|
|
t.Errorf("expected ptr-missing")
|
|
}
|
|
if !hasIssue(issues, CodeFCrDNSMismatch) {
|
|
t.Errorf("expected fcrdns-mismatch")
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_NullSenderRejected(t *testing.T) {
|
|
no := false
|
|
d := &SMTPData{
|
|
Domain: "x",
|
|
MX: MXLookup{Records: []MXRecord{{Target: "mx.x", IPv4: []string{"1.2.3.4"}}}},
|
|
Endpoints: []EndpointProbe{
|
|
{
|
|
Target: "mx.x", IP: "1.2.3.4", Address: "1.2.3.4:25", TCPConnected: true, BannerReceived: true, BannerCode: 220,
|
|
EHLOReceived: true, STARTTLSOffered: true, STARTTLSUpgraded: true,
|
|
PTR: "mx.x", FCrDNSPass: true, HasPipelining: true, Has8BITMIME: true,
|
|
NullSenderAccepted: &no, NullSenderResponse: "550 nope",
|
|
},
|
|
},
|
|
}
|
|
issues := deriveIssues(d)
|
|
is := issueByCode(issues, CodeNullSenderReject)
|
|
if is == nil {
|
|
t.Fatalf("expected null-sender-reject")
|
|
}
|
|
if is.Severity != SeverityCrit {
|
|
t.Errorf("severity: want crit, got %q", is.Severity)
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_PostmasterRejected(t *testing.T) {
|
|
no := false
|
|
d := &SMTPData{
|
|
Domain: "x",
|
|
MX: MXLookup{Records: []MXRecord{{Target: "mx.x", IPv4: []string{"1.2.3.4"}}}},
|
|
Endpoints: []EndpointProbe{
|
|
{
|
|
Target: "mx.x", IP: "1.2.3.4", Address: "1.2.3.4:25", TCPConnected: true, BannerReceived: true, BannerCode: 220,
|
|
EHLOReceived: true, STARTTLSOffered: true, STARTTLSUpgraded: true,
|
|
PTR: "mx.x", FCrDNSPass: true, HasPipelining: true, Has8BITMIME: true,
|
|
PostmasterAccepted: &no, PostmasterResponse: "550 no postmaster",
|
|
},
|
|
},
|
|
}
|
|
if !hasIssue(deriveIssues(d), CodePostmasterReject) {
|
|
t.Fatalf("expected postmaster-reject")
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_NoIPv6(t *testing.T) {
|
|
d := &SMTPData{
|
|
Domain: "x",
|
|
MX: MXLookup{Records: []MXRecord{{Target: "mx.x", IPv4: []string{"1.2.3.4"}}}},
|
|
Coverage: Coverage{HasIPv4: true, HasIPv6: false},
|
|
}
|
|
if !hasIssue(deriveIssues(d), CodeNoIPv6) {
|
|
t.Fatalf("expected no-ipv6 info issue")
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_NoExtensionInfoIssues(t *testing.T) {
|
|
d := &SMTPData{
|
|
Domain: "x",
|
|
MX: MXLookup{Records: []MXRecord{{Target: "mx.x", IPv4: []string{"1.2.3.4"}}}},
|
|
Coverage: Coverage{HasIPv4: true, HasIPv6: true},
|
|
Endpoints: []EndpointProbe{
|
|
{
|
|
Target: "mx.x", IP: "1.2.3.4", Address: "1.2.3.4:25", TCPConnected: true, BannerReceived: true, BannerCode: 220,
|
|
EHLOReceived: true, STARTTLSOffered: true, STARTTLSUpgraded: true,
|
|
PTR: "mx.x", FCrDNSPass: true,
|
|
HasPipelining: false, Has8BITMIME: false,
|
|
},
|
|
},
|
|
}
|
|
issues := deriveIssues(d)
|
|
if !hasIssue(issues, CodeNoPipelining) {
|
|
t.Errorf("expected no-pipelining info")
|
|
}
|
|
if !hasIssue(issues, CodeNo8BITMIME) {
|
|
t.Errorf("expected no-8bitmime info")
|
|
}
|
|
}
|
|
|
|
func TestDeriveIssues_HappyPath(t *testing.T) {
|
|
yes := true
|
|
no := false
|
|
d := &SMTPData{
|
|
Domain: "example.com",
|
|
MX: MXLookup{Records: []MXRecord{{Preference: 10, Target: "mx.example.com", IPv4: []string{"1.2.3.4"}, IPv6: []string{"2001:db8::1"}}}},
|
|
Coverage: Coverage{HasIPv4: true, HasIPv6: true},
|
|
Endpoints: []EndpointProbe{
|
|
{
|
|
Target: "mx.example.com", IP: "1.2.3.4", Address: "1.2.3.4:25",
|
|
TCPConnected: true, BannerReceived: true, BannerCode: 220,
|
|
EHLOReceived: true, STARTTLSOffered: true, STARTTLSUpgraded: true,
|
|
PTR: "mx.example.com", FCrDNSPass: true,
|
|
HasPipelining: true, Has8BITMIME: true,
|
|
NullSenderAccepted: &yes, PostmasterAccepted: &yes, OpenRelay: &no,
|
|
},
|
|
},
|
|
}
|
|
issues := deriveIssues(d)
|
|
if len(issues) != 0 {
|
|
t.Errorf("happy-path should have no issues, got: %+v", issues)
|
|
}
|
|
}
|