package checker import ( "context" "fmt" "strings" 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", } } 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)...) worst := sdk.StatusOK critMsgs, warnMsgs := []string{}, []string{} var firstCritCode, firstWarnCode string for _, is := range issues { switch is.Severity { case SeverityCrit: if worst < sdk.StatusCrit { worst = sdk.StatusCrit } if firstCritCode == "" { firstCritCode = is.Code } critMsgs = append(critMsgs, is.Message) case SeverityWarn: if worst < sdk.StatusWarn { worst = sdk.StatusWarn } if firstWarnCode == "" { firstWarnCode = is.Code } warnMsgs = append(warnMsgs, is.Message) } } // 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, }, } } 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), "native_issues": len(data.Issues), "related_tls_count": len(related), } switch worst { case sdk.StatusOK: 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: meta, } case sdk.StatusWarn: return sdk.CheckState{ Status: sdk.StatusWarn, Message: "SMTP works with warnings: " + joinTop(warnMsgs, 2), Code: firstWarnCode, Meta: meta, } default: return sdk.CheckState{ Status: sdk.StatusCrit, Message: "SMTP broken: " + joinTop(critMsgs, 2), Code: firstCritCode, Meta: meta, } } } func joinTop(msgs []string, n int) string { if len(msgs) == 0 { return "" } if len(msgs) <= n { return strings.Join(msgs, "; ") } return strings.Join(msgs[:n], "; ") + fmt.Sprintf(" (+%d more)", len(msgs)-n) }