--version/-V on the CLI, version in /api/health via Server.SetVersion, Makefile/Dockerfile ldflags stamping from git describe, and a release workflow on v* tags: both binaries for linux/darwin (amd64+arm64) and windows/amd64 with SHA256SUMS attached to the Gitea release (idempotent — reuses an existing hand-written release and skips already-uploaded assets), plus version-tagged Docker images. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
212 lines
6.1 KiB
Go
212 lines
6.1 KiB
Go
// Package config defines the configuration types and defaults for ExploreDNS,
|
|
// along with validation helpers and the CLI usage text.
|
|
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
var (
|
|
ErrInvalidQueryType = errors.New("invalid query type")
|
|
ErrInvalidUDPSize = errors.New("UDP size must be between 512 and 4096")
|
|
ErrInvalidMaxDepth = errors.New("max depth must be between 1 and 100")
|
|
ErrInvalidRetries = errors.New("retries must be between 0 and 10")
|
|
ErrMissingDomain = errors.New("domain is required")
|
|
ErrAlwaysTCPRequiresTCP = errors.New("--always-tcp requires --allow-tcp")
|
|
)
|
|
|
|
type Config struct {
|
|
QueryType string
|
|
RootServer string
|
|
AllRootServers bool
|
|
RootAAAA bool
|
|
FollowAAAA bool
|
|
// DNSUpstream is the upstream resolver used for root server discovery.
|
|
// Format: "host:port" (e.g. "8.8.8.8:53"). Empty means use the system resolver.
|
|
DNSUpstream string
|
|
UDPSize int
|
|
AllowTCP bool
|
|
AlwaysTCP bool
|
|
MaxDepth int
|
|
Retries int
|
|
Fast bool
|
|
Verbose bool
|
|
Debug int
|
|
Quiet bool
|
|
|
|
ShowProgress bool
|
|
ShowResolves bool
|
|
ShowServers bool
|
|
ShowVersions bool
|
|
ShowAllStats bool
|
|
ShowResults bool
|
|
ShowSummaryResults bool
|
|
}
|
|
|
|
func ParseQueryType(s string) (uint16, error) {
|
|
s = strings.ToUpper(s)
|
|
switch s {
|
|
case "A":
|
|
return dns.TypeA, nil
|
|
case "AAAA":
|
|
return dns.TypeAAAA, nil
|
|
case "NS":
|
|
return dns.TypeNS, nil
|
|
case "CNAME":
|
|
return dns.TypeCNAME, nil
|
|
case "MX":
|
|
return dns.TypeMX, nil
|
|
case "TXT":
|
|
return dns.TypeTXT, nil
|
|
case "SOA":
|
|
return dns.TypeSOA, nil
|
|
case "PTR":
|
|
return dns.TypePTR, nil
|
|
case "ANY":
|
|
return dns.TypeANY, nil
|
|
default:
|
|
return 0, fmt.Errorf("%w: %s", ErrInvalidQueryType, s)
|
|
}
|
|
}
|
|
|
|
func (c *Config) Validate() error {
|
|
if _, err := ParseQueryType(c.QueryType); err != nil {
|
|
return err
|
|
}
|
|
|
|
if c.UDPSize < 512 || c.UDPSize > 4096 {
|
|
return fmt.Errorf("%w: %d", ErrInvalidUDPSize, c.UDPSize)
|
|
}
|
|
|
|
if c.MaxDepth < 1 || c.MaxDepth > 100 {
|
|
return fmt.Errorf("%w: %d", ErrInvalidMaxDepth, c.MaxDepth)
|
|
}
|
|
|
|
if c.Retries < 0 || c.Retries > 10 {
|
|
return fmt.Errorf("%w: %d", ErrInvalidRetries, c.Retries)
|
|
}
|
|
|
|
if c.AlwaysTCP && !c.AllowTCP {
|
|
return ErrAlwaysTCPRequiresTCP
|
|
}
|
|
|
|
if c.DNSUpstream != "" {
|
|
if _, _, err := net.SplitHostPort(c.DNSUpstream); err != nil {
|
|
return fmt.Errorf("--dns-upstream %q is not a valid host:port address", c.DNSUpstream)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) GetDomain(args []string) (string, error) {
|
|
if len(args) == 0 {
|
|
return "", ErrMissingDomain
|
|
}
|
|
return args[0], nil
|
|
}
|
|
|
|
// ParseDebugLevel returns the debug verbosity level from the -d and -dd flag values.
|
|
// dd=true → 2 (library debug), d=true → 1 (application debug), neither → 0.
|
|
func ParseDebugLevel(d, dd bool) int {
|
|
if dd {
|
|
return 2
|
|
}
|
|
if d {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func PrintUsage() {
|
|
fmt.Fprintf(os.Stderr, "ExploreDNS - DNS reconnaissance and exploration tool\n\n")
|
|
fmt.Fprintf(os.Stderr, "Usage:\n")
|
|
fmt.Fprintf(os.Stderr, " exploredns [flags] <domain>\n\n")
|
|
fmt.Fprintf(os.Stderr, "Flags:\n")
|
|
|
|
// Ordered slice, not a map: --help output must be stable.
|
|
flagGroups := []struct {
|
|
name string
|
|
flags [][2]string
|
|
}{
|
|
{"Query Options", [][2]string{
|
|
{"--type", "Record type (default a)"},
|
|
{"--root-server", "Initial root server, hostname or IP (default: ask upstream)"},
|
|
{"--all-root-servers", "Traverse from all root servers (default false)"},
|
|
{"--root-aaaa", "Include IPv6 root addresses (not implemented yet)"},
|
|
{"--follow-aaaa", "Only follow AAAA for referrals (not implemented yet)"},
|
|
{"--dns-upstream", "Upstream resolver for root discovery (default: system)"},
|
|
}},
|
|
{"Transport Options", [][2]string{
|
|
{"--udp-size", "EDNS0 buffer size; 512 turns EDNS0 off (default 2048)"},
|
|
{"--allow-tcp", "TCP fallback on truncation (default true)"},
|
|
{"--always-tcp", "Always use TCP (default false)"},
|
|
{"--retries", "Number of 2s retries before timing out (default 2)"},
|
|
}},
|
|
{"Traversal Options", [][2]string{
|
|
{"--max-depth", "Max traversal depth (default 20)"},
|
|
{"--fast", "Fast mode; turn off to be more accurate (default true)"},
|
|
}},
|
|
{"Output Options", [][2]string{
|
|
{"--json", "Emit a single JSON document instead of text"},
|
|
{"--verbose, -v", "Verbose progress ([qname] and <bailiwick> shown)"},
|
|
{"-d, --debug", "Print debug diagnostics to stderr"},
|
|
{"-dd", "Like -d plus library-level debug"},
|
|
{"--quiet, -q", "Suppress the header block"},
|
|
{"--show-progress", "Show traversal progress (default true)"},
|
|
{"--show-resolves", "Show glue resolution progress (default false)"},
|
|
{"--show-servers", "Show servers encountered (default false)"},
|
|
{"--show-versions", "Show server version fingerprints (default true)"},
|
|
{"--show-all-stats", "Show statistics after every node (default false)"},
|
|
{"--show-results", "Show the results (default true)"},
|
|
{"--show-summary-results", "Show the summary results (default true)"},
|
|
}},
|
|
{"General Options", [][2]string{
|
|
{"--version, -V", "Print version and exit"},
|
|
}},
|
|
}
|
|
|
|
for _, group := range flagGroups {
|
|
fmt.Fprintf(os.Stderr, "\n%s:\n", group.name)
|
|
for _, f := range group.flags {
|
|
fmt.Fprintf(os.Stderr, " %-25s %s\n", f[0], f[1])
|
|
}
|
|
}
|
|
fmt.Fprintf(os.Stderr, "\nEvery --show-X flag can be negated with --show-X=false or --no-show-X.\n")
|
|
}
|
|
|
|
func DefaultConfig() *Config {
|
|
return &Config{
|
|
// Lowercase like the reference default (:a); explicit --type values
|
|
// keep the user's case in the "Running query" line.
|
|
QueryType: "a",
|
|
RootServer: "",
|
|
AllRootServers: false,
|
|
RootAAAA: false,
|
|
FollowAAAA: false,
|
|
DNSUpstream: "",
|
|
UDPSize: 2048,
|
|
AllowTCP: true,
|
|
AlwaysTCP: false,
|
|
MaxDepth: 20,
|
|
Retries: 2,
|
|
Fast: true,
|
|
Verbose: false,
|
|
Debug: 0,
|
|
Quiet: false,
|
|
ShowProgress: true,
|
|
ShowResolves: false,
|
|
ShowServers: false,
|
|
ShowVersions: true,
|
|
ShowAllStats: false,
|
|
ShowResults: true,
|
|
ShowSummaryResults: true,
|
|
}
|
|
}
|