Initial commit
This commit is contained in:
commit
d4a59fb9e8
18 changed files with 1439 additions and 0 deletions
138
checker/collect.go
Normal file
138
checker/collect.go
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package checker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
sdk "git.happydns.org/checker-sdk-go/checker"
|
||||
)
|
||||
|
||||
// Collect walks the working zone and records every legacy RR encountered.
|
||||
// We decode the zone as a minimal local shape (rawZone) so the checker stays
|
||||
// free of any happyDomain module dependency. Almost every legacy record
|
||||
// reaches us as an "svcs.Orphan" (happyDomain has no dedicated service for
|
||||
// these types), so the orphan body is the primary path; other service types
|
||||
// are also probed for an embedded RR header on a best-effort basis.
|
||||
func (p *legacyProvider) Collect(ctx context.Context, opts sdk.CheckerOptions) (any, error) {
|
||||
zone, err := readZone(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := &LegacyData{Zone: zone.DomainName}
|
||||
if data.Zone == "" {
|
||||
if name, ok := sdk.GetOption[string](opts, "domain_name"); ok {
|
||||
data.Zone = strings.TrimSuffix(name, ".")
|
||||
}
|
||||
}
|
||||
|
||||
// Sort subdomains so the report ordering is stable across runs and
|
||||
// findings stay diff-friendly when the user replays the check.
|
||||
subs := make([]string, 0, len(zone.Services))
|
||||
for s := range zone.Services {
|
||||
subs = append(subs, s)
|
||||
}
|
||||
sort.Strings(subs)
|
||||
|
||||
for _, sub := range subs {
|
||||
for _, svc := range zone.Services[sub] {
|
||||
data.ServicesScanned++
|
||||
f, perr := inspectService(sub, svc)
|
||||
if perr != nil {
|
||||
data.CollectErrors = append(data.CollectErrors,
|
||||
fmt.Sprintf("%s/%s: %v", displaySubdomain(sub), svc.Type, perr))
|
||||
continue
|
||||
}
|
||||
data.Findings = append(data.Findings, f...)
|
||||
}
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// readZone normalises the "zone" option, which arrives either as a native
|
||||
// *Zone (in-process plugin) or as a JSON object (HTTP path). We round-trip
|
||||
// through json.Marshal in both cases: it costs one allocation and keeps the
|
||||
// rawZone decoder as the single shape contract.
|
||||
func readZone(opts sdk.CheckerOptions) (*rawZone, error) {
|
||||
v, ok := opts["zone"]
|
||||
if !ok || v == nil {
|
||||
return nil, fmt.Errorf("missing 'zone' option (AutoFillZone): the host did not provide a working zone")
|
||||
}
|
||||
raw, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("re-marshal zone option: %w", err)
|
||||
}
|
||||
z := &rawZone{}
|
||||
if err := json.Unmarshal(raw, z); err != nil {
|
||||
return nil, fmt.Errorf("decode zone option: %w", err)
|
||||
}
|
||||
return z, nil
|
||||
}
|
||||
|
||||
// inspectService returns one finding per legacy record carried by the
|
||||
// service. Returns (nil, nil) for non-legacy services (the common case).
|
||||
func inspectService(sub string, svc rawService) ([]Finding, error) {
|
||||
hdr, ok, err := extractRRHeader(svc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if _, deprecated := deprecatedTypes[hdr.Rrtype]; !deprecated {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return []Finding{{
|
||||
Subdomain: sub,
|
||||
Name: hdr.Name,
|
||||
Rrtype: hdr.Rrtype,
|
||||
TypeName: typeLabel(hdr.Rrtype),
|
||||
ServiceType: svc.Type,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
// extractRRHeader pulls the RR header from a service body. Only svcs.Orphan
|
||||
// exposes such a header on the wire today; other service types are skipped
|
||||
// silently so the common case (MX, A, TXT, …) does not pollute CollectErrors.
|
||||
// When the service *is* an orphan but the body fails to decode, the error is
|
||||
// propagated so the operator sees the malformed entry in the report.
|
||||
func extractRRHeader(svc rawService) (orphanHdr, bool, error) {
|
||||
if len(svc.Service) == 0 {
|
||||
return orphanHdr{}, false, nil
|
||||
}
|
||||
|
||||
if svc.Type != "svcs.Orphan" {
|
||||
return orphanHdr{}, false, nil
|
||||
}
|
||||
|
||||
var ob orphanBody
|
||||
if err := json.Unmarshal(svc.Service, &ob); err != nil {
|
||||
return orphanHdr{}, false, fmt.Errorf("decode orphan body: %w", err)
|
||||
}
|
||||
if ob.Record.Hdr.Rrtype == 0 {
|
||||
return orphanHdr{}, false, nil
|
||||
}
|
||||
return orphanHdr(ob.Record.Hdr), true, nil
|
||||
}
|
||||
|
||||
// orphanHdr is a flat copy of orphanBody.Record.Hdr so callers don't have
|
||||
// to know about the JSON nesting.
|
||||
type orphanHdr struct {
|
||||
Name string `json:"Name"`
|
||||
Rrtype uint16 `json:"Rrtype"`
|
||||
}
|
||||
|
||||
// displaySubdomain renders the apex as "@" so error messages match the
|
||||
// convention used everywhere else in happyDomain.
|
||||
func displaySubdomain(s string) string {
|
||||
if s == "" || s == "@" {
|
||||
return "@"
|
||||
}
|
||||
return s
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue