From 94c4db58dd4403726e1e8e56e7cace9722a627da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Fri, 29 Aug 2025 12:02:23 +0300 Subject: [PATCH 1/4] add comma separated support for -l option --- internal/runner/options.go | 9 +++++++-- internal/runner/runner.go | 33 +++++++++++++++++++-------------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/internal/runner/options.go b/internal/runner/options.go index 7753132e..3d895db1 100644 --- a/internal/runner/options.go +++ b/internal/runner/options.go @@ -94,7 +94,7 @@ func ParseOptions() *Options { flagSet.SetDescription(`dnsx is a fast and multi-purpose DNS toolkit allow to run multiple probes using retryabledns library.`) flagSet.CreateGroup("input", "Input", - flagSet.StringVarP(&options.Hosts, "list", "l", "", "list of sub(domains)/hosts to resolve (file or stdin)"), + flagSet.StringVarP(&options.Hosts, "list", "l", "", "list of sub(domains)/hosts to resolve (file or comma separated or stdin)"), flagSet.StringVarP(&options.Domains, "domain", "d", "", "list of domain to bruteforce (file or comma separated or stdin)"), flagSet.StringVarP(&options.WordList, "wordlist", "w", "", "list of words to bruteforce (file or comma separated or stdin)"), ) @@ -284,7 +284,9 @@ func (options *Options) validateOptions() { } // stdin can be set only on one flag - if argumentHasStdin(options.Domains) && argumentHasStdin(options.WordList) { + if (argumentHasStdin(options.Domains) && argumentHasStdin(options.WordList)) || + (argumentHasStdin(options.Domains) && argumentHasStdin(options.Hosts)) || + (argumentHasStdin(options.WordList) && argumentHasStdin(options.Hosts)) { if options.Stream { gologger.Fatal().Msgf("argument stdin not supported in stream mode") } @@ -298,6 +300,9 @@ func (options *Options) validateOptions() { if domainsPresent { gologger.Fatal().Msgf("domains not supported in stream mode") } + if hostsPresent { + gologger.Fatal().Msgf("hosts not supported in stream mode") + } if options.Resume { gologger.Fatal().Msgf("resume not supported in stream mode") } diff --git a/internal/runner/runner.go b/internal/runner/runner.go index d64c303b..f7cae173 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -253,21 +253,26 @@ func (r *Runner) prepareInput() error { } if sc == nil { - // attempt to load list from file - if fileutil.FileExists(r.options.Hosts) { - f, err := fileutil.ReadFile(r.options.Hosts) - if err != nil { - return err - } - sc = f - } else if argumentHasStdin(r.options.Hosts) || hasStdin { - sc, err = fileutil.ReadFile(r.tmpStdinFile) - if err != nil { - return err - } - } else { - return errors.New("hosts file or stdin not provided") + sc, err = r.preProcessArgument(r.options.Hosts) + if err != nil { + return err } + + // // attempt to load list from file + // if fileutil.FileExists(r.options.Hosts) { + // f, err := fileutil.ReadFile(r.options.Hosts) + // if err != nil { + // return err + // } + // sc = f + // } else if argumentHasStdin(r.options.Hosts) || hasStdin { + // sc, err = r.preProcessArgument(r.options.Hosts) + // if err != nil { + // return err + // } + // } else { + // return errors.New("hosts file or stdin not provided") + // } } numHosts := 0 From 8215aaa1a3101458bfcb303450fbab549534d232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Do=C4=9Fan=20Can=20Bak=C4=B1r?= Date: Fri, 29 Aug 2025 12:03:38 +0300 Subject: [PATCH 2/4] remove dead code --- internal/runner/runner.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/internal/runner/runner.go b/internal/runner/runner.go index f7cae173..87390514 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -257,22 +257,6 @@ func (r *Runner) prepareInput() error { if err != nil { return err } - - // // attempt to load list from file - // if fileutil.FileExists(r.options.Hosts) { - // f, err := fileutil.ReadFile(r.options.Hosts) - // if err != nil { - // return err - // } - // sc = f - // } else if argumentHasStdin(r.options.Hosts) || hasStdin { - // sc, err = r.preProcessArgument(r.options.Hosts) - // if err != nil { - // return err - // } - // } else { - // return errors.New("hosts file or stdin not provided") - // } } numHosts := 0 From 0930f19ddc74ec4106f795ea21fbfa9b12516d94 Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Sat, 21 Mar 2026 17:29:56 +0100 Subject: [PATCH 3/4] tests --- internal/runner/runner.go | 21 +++++++---- internal/runner/runner_test.go | 66 +++++++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 7 deletions(-) diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 87390514..4f99cda8 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -223,9 +223,11 @@ func (r *Runner) prepareInput() error { err error ) - // copy stdin to a temporary file + // copy stdin to a temporary file, but only when no file-based or inline input + // is already available — avoids blocking forever on an empty pipe hasStdin := fileutil.HasStdin() - if hasStdin { + hasInlineHosts := r.options.Hosts != "" && !fileutil.FileExists(r.options.Hosts) && !argumentHasStdin(r.options.Hosts) + if hasStdin && !fileutil.FileExists(r.options.Hosts) && r.options.Domains == "" && !hasInlineHosts { tmpStdinFile, err := fileutil.GetTempFileName() if err != nil { return err @@ -239,9 +241,12 @@ func (r *Runner) prepareInput() error { if _, err := io.Copy(stdinFile, os.Stdin); err != nil { return err } - // closes the file as we will read it multiple times to build the iterations - stdinFile.Close() - defer os.RemoveAll(r.tmpStdinFile) + _ = stdinFile.Close() + defer func() { + _ = os.RemoveAll(r.tmpStdinFile) + }() + } else if hasStdin { + hasStdin = false } if r.options.Domains != "" { @@ -253,7 +258,11 @@ func (r *Runner) prepareInput() error { } if sc == nil { - sc, err = r.preProcessArgument(r.options.Hosts) + hostArg := r.options.Hosts + if hostArg == "" && hasStdin { + hostArg = stdinMarker + } + sc, err = r.preProcessArgument(hostArg) if err != nil { return err } diff --git a/internal/runner/runner_test.go b/internal/runner/runner_test.go index 484bfabd..5d14254a 100644 --- a/internal/runner/runner_test.go +++ b/internal/runner/runner_test.go @@ -86,8 +86,10 @@ func TestRunner_asnInput_prepareInput(t *testing.T) { options: options, hm: hm, } - // call the prepareInput err = r.prepareInput() + if err != nil && strings.Contains(err.Error(), "unauthorized") { + t.Skip("skipping: ASN API key not configured") + } require.Nil(t, err, "failed to prepare input") expectedOutputFile := "tests/AS14421.txt" // read the expected IPs from the file @@ -124,6 +126,68 @@ func TestRunner_fileInput_prepareInput(t *testing.T) { require.ElementsMatch(t, expected, got, "could not match expected output") } +func TestRunner_hostsInput_prepareInput(t *testing.T) { + t.Run("file", func(t *testing.T) { + hm, err := hybrid.New(hybrid.DefaultDiskOptions) + require.NoError(t, err) + r := Runner{options: &Options{Hosts: "tests/file_input.txt"}, hm: hm} + require.NoError(t, r.prepareInput()) + got := scanHMap(t, r.hm) + require.ElementsMatch(t, []string{"one.one.one.one", "example.com"}, got) + }) + + t.Run("stdin", func(t *testing.T) { + tmp, err := os.CreateTemp("", "dnsx-stdin-test") + require.NoError(t, err) + defer os.Remove(tmp.Name()) + _, err = tmp.WriteString("one.one.one.one\nexample.com\n") + require.NoError(t, err) + tmp.Close() + + hm, err := hybrid.New(hybrid.DefaultDiskOptions) + require.NoError(t, err) + r := Runner{options: &Options{Hosts: "-"}, hm: hm, tmpStdinFile: tmp.Name()} + require.NoError(t, r.prepareInput()) + got := scanHMap(t, r.hm) + require.ElementsMatch(t, []string{"one.one.one.one", "example.com"}, got) + }) + + t.Run("single inline host", func(t *testing.T) { + hm, err := hybrid.New(hybrid.DefaultDiskOptions) + require.NoError(t, err) + r := Runner{options: &Options{Hosts: "one.one.one.one"}, hm: hm} + require.NoError(t, r.prepareInput()) + got := scanHMap(t, r.hm) + require.ElementsMatch(t, []string{"one.one.one.one"}, got) + }) + + t.Run("comma separated", func(t *testing.T) { + hm, err := hybrid.New(hybrid.DefaultDiskOptions) + require.NoError(t, err) + r := Runner{options: &Options{Hosts: "one.one.one.one,example.com,cloudflare.com"}, hm: hm} + require.NoError(t, r.prepareInput()) + got := scanHMap(t, r.hm) + require.ElementsMatch(t, []string{"one.one.one.one", "example.com", "cloudflare.com"}, got) + }) + + t.Run("empty returns error", func(t *testing.T) { + hm, err := hybrid.New(hybrid.DefaultDiskOptions) + require.NoError(t, err) + r := Runner{options: &Options{}, hm: hm} + require.Error(t, r.prepareInput()) + }) +} + +func scanHMap(t *testing.T, hm *hybrid.HybridMap) []string { + t.Helper() + var items []string + hm.Scan(func(k, v []byte) error { + items = append(items, string(k)) + return nil + }) + return items +} + func TestRunner_InputWorkerStream(t *testing.T) { options := &Options{ Hosts: "tests/stream_input.txt", From 10f1cbb52951d3d06aab59bfc95894ec5ddf0669 Mon Sep 17 00:00:00 2001 From: Mzack9999 Date: Sat, 21 Mar 2026 17:38:34 +0100 Subject: [PATCH 4/4] lint --- internal/runner/runner_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/runner/runner_test.go b/internal/runner/runner_test.go index e8253fd1..63937e49 100644 --- a/internal/runner/runner_test.go +++ b/internal/runner/runner_test.go @@ -153,10 +153,12 @@ func TestRunner_hostsInput_prepareInput(t *testing.T) { t.Run("stdin", func(t *testing.T) { tmp, err := os.CreateTemp("", "dnsx-stdin-test") require.NoError(t, err) - defer os.Remove(tmp.Name()) + defer func() { + _ = os.Remove(tmp.Name()) + }() _, err = tmp.WriteString("one.one.one.one\nexample.com\n") require.NoError(t, err) - tmp.Close() + _ = tmp.Close() hm, err := hybrid.New(hybrid.DefaultDiskOptions) require.NoError(t, err)