package main import ( "context" "flag" "fmt" "os" "time" "gitea.hansenits.com.au/hits/ExploreDNS/internal/config" "gitea.hansenits.com.au/hits/ExploreDNS/internal/dns" "gitea.hansenits.com.au/hits/ExploreDNS/internal/output" "gitea.hansenits.com.au/hits/ExploreDNS/internal/traverse" ) func main() { cfg := config.DefaultConfig() queryType := flag.String("type", cfg.QueryType, "Record type (A, AAAA, NS, CNAME, MX, TXT, SOA, PTR, ANY)") rootServer := flag.String("root-server", cfg.RootServer, "Initial root server (hostname or IP)") allRootServers := flag.Bool("all-root-servers", cfg.AllRootServers, "Traverse from all root servers") rootAAAA := flag.Bool("root-aaaa", cfg.RootAAAA, "Include IPv6 root addresses (not implemented yet)") followAAAA := flag.Bool("follow-aaaa", cfg.FollowAAAA, "Only follow AAAA for referrals (not implemented yet)") dnsUpstream := flag.String("dns-upstream", cfg.DNSUpstream, "Upstream resolver for root discovery (e.g. 8.8.8.8:53, default: system)") udpSize := flag.Int("udp-size", cfg.UDPSize, "EDNS0 buffer size (512-4096; 512 turns EDNS0 off)") allowTCP := flag.Bool("allow-tcp", cfg.AllowTCP, "TCP fallback on truncation") alwaysTCP := flag.Bool("always-tcp", cfg.AlwaysTCP, "Always use TCP") maxDepth := flag.Int("max-depth", cfg.MaxDepth, "Max traversal depth (1-100)") retries := flag.Int("retries", cfg.Retries, "Number of 2s retries before timing out (0-10)") fast := flag.Bool("fast", cfg.Fast, "Fast mode; turn off to be more accurate") jsonOutput := flag.Bool("json", false, "Output results as JSON") // Verbose: long and short form share the same variable. var verboseVal bool flag.BoolVar(&verboseVal, "verbose", cfg.Verbose, "Verbose output") flag.BoolVar(&verboseVal, "v", cfg.Verbose, "Verbose output (shorthand)") // Debug: -d prints debug diagnostics; -dd additionally enables library // debug (the Go flag package cannot stack -d -d like the Ruby CLI). var dFlag, ddFlag bool flag.BoolVar(&dFlag, "d", false, "Print debug diagnostics to stderr") flag.BoolVar(&dFlag, "debug", false, "Print debug diagnostics to stderr") flag.BoolVar(&ddFlag, "dd", false, "Like -d plus library-level debug") // Quiet: long and short form share the same variable. var quietVal bool flag.BoolVar(&quietVal, "quiet", cfg.Quiet, "Suppress the header block") flag.BoolVar(&quietVal, "q", cfg.Quiet, "Suppress the header block (shorthand)") // Each show flag defaults to the config default, so --show-X and // --show-X=false both take effect directly; the --no-show-X aliases force // the value off, matching the Ruby --[no-]show-X switches. showProgress := flag.Bool("show-progress", cfg.ShowProgress, "Show traversal progress") noShowProgress := flag.Bool("no-show-progress", false, "Hide traversal progress") showResolves := flag.Bool("show-resolves", cfg.ShowResolves, "Show glue resolution progress") noShowResolves := flag.Bool("no-show-resolves", false, "Hide glue resolution progress") showServers := flag.Bool("show-servers", cfg.ShowServers, "Show servers encountered") noShowServers := flag.Bool("no-show-servers", false, "Hide servers encountered") showVersions := flag.Bool("show-versions", cfg.ShowVersions, "Show server version fingerprints") noShowVersions := flag.Bool("no-show-versions", false, "Hide server version fingerprints") showAllStats := flag.Bool("show-all-stats", cfg.ShowAllStats, "Show statistics after every node") noShowAllStats := flag.Bool("no-show-all-stats", false, "Hide per-node statistics") showResults := flag.Bool("show-results", cfg.ShowResults, "Show query results") noShowResults := flag.Bool("no-show-results", false, "Hide query results") showSummaryResults := flag.Bool("show-summary-results", cfg.ShowSummaryResults, "Show summary of results") noShowSummaryResults := flag.Bool("no-show-summary-results", false, "Hide summary of results") flag.Usage = config.PrintUsage flag.Parse() args := flag.Args() cfg.QueryType = *queryType cfg.RootServer = *rootServer cfg.AllRootServers = *allRootServers cfg.RootAAAA = *rootAAAA cfg.FollowAAAA = *followAAAA cfg.DNSUpstream = *dnsUpstream cfg.UDPSize = *udpSize cfg.AllowTCP = *allowTCP cfg.AlwaysTCP = *alwaysTCP cfg.MaxDepth = *maxDepth cfg.Retries = *retries cfg.Fast = *fast cfg.Verbose = verboseVal cfg.Debug = config.ParseDebugLevel(dFlag, ddFlag) cfg.Quiet = quietVal cfg.ShowProgress = *showProgress && !*noShowProgress cfg.ShowResolves = *showResolves && !*noShowResolves cfg.ShowServers = *showServers && !*noShowServers cfg.ShowVersions = *showVersions && !*noShowVersions cfg.ShowAllStats = *showAllStats && !*noShowAllStats cfg.ShowResults = *showResults && !*noShowResults cfg.ShowSummaryResults = *showSummaryResults && !*noShowSummaryResults if err := cfg.Validate(); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } domain, err := cfg.GetDomain(args) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } queryTypeValue, err := config.ParseQueryType(cfg.QueryType) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } queryConfig := &dns.QueryConfig{ UDPSize: cfg.UDPSize, Timeout: 2 * time.Second, Retries: cfg.Retries, UseTCP: cfg.AlwaysTCP, AllowTCP: cfg.AllowTCP, } rootConfig := &dns.RootDiscoveryConfig{ IncludeAAAA: cfg.RootAAAA, // --root-server accepts a hostname or an IP literal. Server: cfg.RootServer, AllRoots: cfg.AllRootServers, Resolver: cfg.DNSUpstream, Query: queryConfig, } traverserConfig := &traverse.TraverserConfig{ MaxDepth: cfg.MaxDepth, QueryType: queryTypeValue, RootConfig: rootConfig, QueryConfig: queryConfig, Fast: cfg.Fast, } traverser := traverse.NewTraverser(traverserConfig) if cfg.Debug > 0 { fmt.Fprintf(os.Stderr, "Debug: Config loaded\n") fmt.Fprintf(os.Stderr, " Domain: %s\n", domain) fmt.Fprintf(os.Stderr, " Query Type: %s\n", cfg.QueryType) fmt.Fprintf(os.Stderr, " Max Depth: %d\n", cfg.MaxDepth) fmt.Fprintf(os.Stderr, " UDP Size: %d\n", cfg.UDPSize) fmt.Fprintf(os.Stderr, " Retries: %d\n", cfg.Retries) fmt.Fprintf(os.Stderr, " Allow TCP: %v\n", cfg.AllowTCP) fmt.Fprintf(os.Stderr, " Always TCP: %v\n", cfg.AlwaysTCP) fmt.Fprintf(os.Stderr, " Fast Mode: %v\n", cfg.Fast) if cfg.DNSUpstream != "" { fmt.Fprintf(os.Stderr, " DNS Upstream: %s\n", cfg.DNSUpstream) } } outFmt := output.FormatText if *jsonOutput { outFmt = output.FormatJSON } outCfg := &output.Config{ Format: outFmt, Domain: domain, QueryType: cfg.QueryType, Fast: cfg.Fast, AllRootServers: cfg.AllRootServers, UDPSize: cfg.UDPSize, Retries: cfg.Retries, MaxDepth: cfg.MaxDepth, AllowTCP: cfg.AllowTCP, AlwaysTCP: cfg.AlwaysTCP, ShowProgress: cfg.ShowProgress, ShowResolves: cfg.ShowResolves, ShowServers: cfg.ShowServers, ShowVersions: cfg.ShowVersions, ShowAllStats: cfg.ShowAllStats, ShowResults: cfg.ShowResults, ShowSummaryResults: cfg.ShowSummaryResults, Verbose: cfg.Verbose, Quiet: cfg.Quiet, Color: output.ColorEnabled(os.Stdout), Debug: cfg.Debug, } ctx := context.Background() formatter := output.NewFormatter(outCfg, os.Stdout) _, err = output.RunTraversal(ctx, traverser, outCfg, formatter, domain) if err != nil { fmt.Fprintf(os.Stderr, "Error: traversal failed: %v\n", err) os.Exit(1) } if cfg.Debug > 0 { fmt.Fprintf(os.Stderr, "Debug: Traversal completed\n") } }