// 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") ErrInvalidRootServer = errors.New("invalid root server IP address") ) 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 } ip := net.ParseIP(c.RootServer) if ip == nil { return nil, fmt.Errorf("%w: %s", ErrInvalidRootServer, c.RootServer) } return ip, 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] \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, } }