CI / test (pull_request) Waiting to run
- Created internal/config/config.go with Config struct containing all 15 CLI flags - Added ParseQueryType() for validating record types (A, AAAA, NS, CNAME, MX, TXT, SOA, PTR, ANY) - Added ParseUDPSize() with validation (512-4096) - Added ParseMaxDepth() with validation (1-100) - Added ParseRetries() with validation (0-10) - Added Validate() for cross-field validation (e.g., --always-tcp requires --allow-tcp) - Added GetDomain() for validating the required positional argument - Added PrintUsage() displaying all flags with --no- variants for display options - Updated cmd/exploredns/main.go with flag definitions using the standard flag package - Proper flag parsing and config population - Manual parsing of debug level (supports -dd) - Support for --no-* display flags (e.g., --no-show-progress) - Config validation before execution - Integration with traversal engine using dns.QueryConfig and traverse.TraverserConfig - Clean error handling with descriptive messages All flags implemented: - --type TYPE (default A) - --root-server HOST - --all-root-servers - --root-aaaa - --follow-aaaa - --udp-size SIZE (default 2048) - --allow-tcp (default true) - --always-tcp - --max-depth DEPTH (default 20) - --retries TIMES (default 2) - --fast (default true) - --verbose, -v - --debug, -d (stackable: -dd) - --quiet, -q - --show-progress, --no-show-progress - --show-resolves, --no-show-resolves - --show-servers, --no-show-servers - --show-versions, --no-show-versions - --show-all-stats, --no-show-all-stats - --show-results, --no-show-results - --show-summary-results, --no-show-summary-results Validation: Invalid inputs produce clear error messages, exploredns --help displays usage with all options, all existing tests pass Co-authored-by: multica-agent <github@multica.ai>
221 lines
5.8 KiB
Go
221 lines
5.8 KiB
Go
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
|
|
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 ParseUDPSize(s string) (int, error) {
|
|
var size int
|
|
if _, err := fmt.Sscanf(s, "%d", &size); err != nil {
|
|
return 0, fmt.Errorf("%w: %s", ErrInvalidUDPSize, s)
|
|
}
|
|
if size < 512 || size > 4096 {
|
|
return 0, fmt.Errorf("%w: %d (must be 512-4096)", ErrInvalidUDPSize, size)
|
|
}
|
|
return size, nil
|
|
}
|
|
|
|
func ParseMaxDepth(s string) (int, error) {
|
|
var depth int
|
|
if _, err := fmt.Sscanf(s, "%d", &depth); err != nil {
|
|
return 0, fmt.Errorf("%w: %s", ErrInvalidMaxDepth, s)
|
|
}
|
|
if depth < 1 || depth > 100 {
|
|
return 0, fmt.Errorf("%w: %d (must be 1-100)", ErrInvalidMaxDepth, depth)
|
|
}
|
|
return depth, nil
|
|
}
|
|
|
|
func ParseRetries(s string) (int, error) {
|
|
var retries int
|
|
if _, err := fmt.Sscanf(s, "%d", &retries); err != nil {
|
|
return 0, fmt.Errorf("%w: %s", ErrInvalidRetries, s)
|
|
}
|
|
if retries < 0 || retries > 10 {
|
|
return 0, fmt.Errorf("%w: %d (must be 0-10)", ErrInvalidRetries, retries)
|
|
}
|
|
return retries, nil
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) GetDomain(args []string) (string, error) {
|
|
if len(args) == 0 {
|
|
return "", ErrMissingDomain
|
|
}
|
|
return args[0], nil
|
|
}
|
|
|
|
func (c *Config) ParseRootServer() (net.IP, error) {
|
|
if c.RootServer == "" {
|
|
return nil, nil
|
|
}
|
|
return net.ParseIP(c.RootServer), nil
|
|
}
|
|
|
|
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")
|
|
|
|
flagGroups := map[string][][2]string{
|
|
"Query Options": {
|
|
{"--type", "Record type (default A)"},
|
|
{"--root-server", "Override root server"},
|
|
{"--all-root-servers", "Use all 13 root servers"},
|
|
{"--root-aaaa", "Include IPv6 root addresses"},
|
|
{"--follow-aaaa", "Only follow AAAA for referrals"},
|
|
},
|
|
"Transport Options": {
|
|
{"--udp-size", "EDNS0 buffer size (default 2048)"},
|
|
{"--allow-tcp", "TCP fallback on truncation (default true)"},
|
|
{"--always-tcp", "Always use TCP"},
|
|
{"--retries", "Retry count (default 2)"},
|
|
},
|
|
"Traversal Options": {
|
|
{"--max-depth", "Max traversal depth (default 20)"},
|
|
{"--fast", "Fast mode: reuse earlier branch cache (default true)"},
|
|
},
|
|
"Output Options": {
|
|
{"--verbose, -v", "Verbose output"},
|
|
{"--debug, -d", "Debug mode (stackable: -dd for library debug)"},
|
|
{"--quiet, -q", "Suppress supplementary info"},
|
|
{"--show-progress", "Show traversal progress"},
|
|
{"--no-show-progress", "Hide traversal progress"},
|
|
{"--show-resolves", "Show glue resolution details"},
|
|
{"--no-show-resolves", "Hide glue resolution details"},
|
|
{"--show-servers", "Show servers queried"},
|
|
{"--no-show-servers", "Hide servers queried"},
|
|
{"--show-versions", "Show server versions"},
|
|
{"--no-show-versions", "Hide server versions"},
|
|
{"--show-all-stats", "Show all statistics"},
|
|
{"--no-show-all-stats", "Hide all statistics"},
|
|
{"--show-results", "Show query results"},
|
|
{"--no-show-results", "Hide query results"},
|
|
{"--show-summary-results", "Show summary of results"},
|
|
{"--no-show-summary-results", "Hide summary of results"},
|
|
},
|
|
}
|
|
|
|
for group, flags := range flagGroups {
|
|
fmt.Fprintf(os.Stderr, "\n%s:\n", group)
|
|
for _, f := range flags {
|
|
fmt.Fprintf(os.Stderr, " %-25s %s\n", f[0], f[1])
|
|
}
|
|
}
|
|
}
|
|
|
|
func DefaultConfig() *Config {
|
|
return &Config{
|
|
QueryType: "A",
|
|
RootServer: "",
|
|
AllRootServers: false,
|
|
RootAAAA: false,
|
|
FollowAAAA: false,
|
|
UDPSize: 2048,
|
|
AllowTCP: true,
|
|
AlwaysTCP: false,
|
|
MaxDepth: 20,
|
|
Retries: 2,
|
|
Fast: true,
|
|
Verbose: false,
|
|
Debug: 0,
|
|
Quiet: false,
|
|
ShowProgress: true,
|
|
ShowResolves: true,
|
|
ShowServers: true,
|
|
ShowVersions: true,
|
|
ShowAllStats: true,
|
|
ShowResults: true,
|
|
ShowSummaryResults: true,
|
|
}
|
|
} |