package checker import ( "context" "fmt" sdk "git.happydns.org/checker-sdk-go/checker" ) // Rules returns one rule per registered Source. The host can enable or // disable each independently, and each rule owns the option fields its // Source contributes (CheckRuleWithOptions). The rule name is the // Source ID; downstream a disabled rule both skips evaluation and // skips the underlying network call in Collect. func Rules() []sdk.CheckRule { srcs := Sources() rules := make([]sdk.CheckRule, 0, len(srcs)) for _, s := range srcs { rules = append(rules, &sourceRule{src: s}) } return rules } type sourceRule struct { src Source } func (r *sourceRule) Name() string { return r.src.ID() } func (r *sourceRule) Description() string { return fmt.Sprintf("%s reputation lookup. Emits Critical/Warning when the source flags the domain, OK when clean, Info when the source is disabled or not configured, and Warning on transient query errors.", r.src.Name()) } func (r *sourceRule) Options() sdk.CheckerOptionsDocumentation { o := r.src.Options() return sdk.CheckerOptionsDocumentation{ AdminOpts: o.Admin, UserOpts: o.User, } } func (r *sourceRule) Evaluate(ctx context.Context, obs sdk.ObservationGetter, opts sdk.CheckerOptions) []sdk.CheckState { var data BlacklistData if err := obs.Get(ctx, ObservationKeyBlacklist, &data); err != nil { return []sdk.CheckState{{ Status: sdk.StatusError, Message: fmt.Sprintf("failed to get observation: %v", err), Code: "blacklist_obs_error", }} } var out []sdk.CheckState for _, res := range data.Results { if res.SourceID != r.src.ID() { continue } out = append(out, evaluateOne(res, r.src)) } if len(out) == 0 { return []sdk.CheckState{{ Status: sdk.StatusUnknown, Message: r.src.Name() + ": no result (source skipped or disabled).", Code: "source_disabled", }} } return out } func evaluateOne(r SourceResult, src Source) sdk.CheckState { subj := r.SourceName if r.Subject != "" && r.Subject != r.SourceName { subj = r.SourceName + " / " + r.Subject } listed, severity := src.Evaluate(r) switch { case !r.Enabled: return sdk.CheckState{ Status: sdk.StatusUnknown, Subject: subj, Message: subj + ": disabled or not configured.", Code: "source_disabled", } case r.BlockedQuery: return sdk.CheckState{ Status: sdk.StatusError, Subject: subj, Message: fmt.Sprintf("%s: resolver is blocked, result unreliable: %s", subj, joinNonEmpty(r.Reasons, "; ")), Code: "source_resolver_blocked", } case r.Error != "": return sdk.CheckState{ Status: sdk.StatusWarn, Subject: subj, Message: subj + ": query failed: " + r.Error, Code: "source_error", } case listed: return sdk.CheckState{ Status: severityToStatus(severity), Subject: subj, Message: fmt.Sprintf("Listed in %s: %s", subj, joinNonEmpty(r.Reasons, "; ")), Code: "source_listed", Meta: map[string]any{ "source_id": r.SourceID, "reasons": r.Reasons, "lookup_url": r.LookupURL, "removal_url": r.RemovalURL, "reference": r.Reference, }, } default: return sdk.CheckState{ Status: sdk.StatusOK, Subject: subj, Message: subj + ": clean.", Code: "source_clean", } } } func severityToStatus(sev string) sdk.Status { switch sev { case SeverityCrit: return sdk.StatusCrit case SeverityWarn: return sdk.StatusWarn case SeverityInfo: return sdk.StatusInfo case SeverityOK: return sdk.StatusOK } return sdk.StatusCrit }