103 lines
2.6 KiB
Go
103 lines
2.6 KiB
Go
package checker
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
sdk "git.happydns.org/checker-sdk-go/checker"
|
|
)
|
|
|
|
func Rule() sdk.CheckRule {
|
|
return &smtpRule{}
|
|
}
|
|
|
|
type smtpRule struct{}
|
|
|
|
func (r *smtpRule) Name() string {
|
|
return "smtp_server"
|
|
}
|
|
|
|
func (r *smtpRule) Description() string {
|
|
return "Checks MX discovery, SMTP connectivity, STARTTLS, PTR/FCrDNS and mail-acceptance posture for an email-receiving domain"
|
|
}
|
|
|
|
func (r *smtpRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState {
|
|
var data SMTPData
|
|
if err := obs.Get(ctx, ObservationKeySMTP, &data); err != nil {
|
|
return []sdk.CheckState{{
|
|
Status: sdk.StatusError,
|
|
Message: fmt.Sprintf("failed to load SMTP observation: %v", err),
|
|
Code: "smtp.observation_error",
|
|
}}
|
|
}
|
|
|
|
// Null-MX is a *declared* refusal of mail; it's an INFO, not a fail.
|
|
if data.MX.NullMX {
|
|
return []sdk.CheckState{{
|
|
Status: sdk.StatusInfo,
|
|
Message: "Domain refuses all email via null MX (RFC 7505).",
|
|
Code: CodeNullMX,
|
|
Meta: map[string]any{
|
|
"null_mx": true,
|
|
},
|
|
}}
|
|
}
|
|
|
|
issues := append([]Issue(nil), data.Issues...)
|
|
|
|
// Fold related TLS observations so cert/chain problems show up on the
|
|
// SMTP service page without requiring the user to open a separate
|
|
// report.
|
|
related, _ := obs.GetRelated(ctx, TLSRelatedKey)
|
|
issues = append(issues, tlsIssuesFromRelated(related)...)
|
|
|
|
if len(issues) == 0 {
|
|
return []sdk.CheckState{{
|
|
Status: sdk.StatusOK,
|
|
Message: fmt.Sprintf("SMTP operational (%d MX, %d endpoints, TLS=%v)", len(data.MX.Records), len(data.Endpoints), data.Coverage.AllSTARTTLS),
|
|
Code: "smtp.ok",
|
|
Meta: map[string]any{
|
|
"has_ipv4": data.Coverage.HasIPv4,
|
|
"has_ipv6": data.Coverage.HasIPv6,
|
|
"any_starttls": data.Coverage.AnySTARTTLS,
|
|
"all_starttls": data.Coverage.AllSTARTTLS,
|
|
"all_accept_mail": data.Coverage.AllAcceptMail,
|
|
"mx_count": len(data.MX.Records),
|
|
"endpoint_count": len(data.Endpoints),
|
|
},
|
|
}}
|
|
}
|
|
|
|
out := make([]sdk.CheckState, 0, len(issues))
|
|
for _, is := range issues {
|
|
status := sdk.StatusInfo
|
|
switch is.Severity {
|
|
case SeverityCrit:
|
|
status = sdk.StatusCrit
|
|
case SeverityWarn:
|
|
status = sdk.StatusWarn
|
|
}
|
|
subject := is.Endpoint
|
|
if subject == "" {
|
|
subject = is.Target
|
|
}
|
|
meta := map[string]any{}
|
|
if is.Fix != "" {
|
|
meta["fix"] = is.Fix
|
|
}
|
|
if is.Endpoint != "" {
|
|
meta["endpoint"] = is.Endpoint
|
|
}
|
|
if is.Target != "" {
|
|
meta["target"] = is.Target
|
|
}
|
|
out = append(out, sdk.CheckState{
|
|
Status: status,
|
|
Message: is.Message,
|
|
Code: is.Code,
|
|
Subject: subject,
|
|
Meta: meta,
|
|
})
|
|
}
|
|
return out
|
|
}
|