Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions internal/runner/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,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)"),
)
Expand Down Expand Up @@ -296,7 +296,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")
}
Expand All @@ -310,6 +312,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")
}
Expand Down
28 changes: 11 additions & 17 deletions internal/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,10 +285,11 @@ func (r *Runner) prepareInput() error {
err error
)

// copy stdin to a temporary file, but only when no file-based input
// is already available avoids blocking forever on an empty pipe
// 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 && !fileutil.FileExists(r.options.Hosts) && r.options.Domains == "" {
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
Expand Down Expand Up @@ -319,20 +320,13 @@ 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")
hostArg := r.options.Hosts
if hostArg == "" && hasStdin {
hostArg = stdinMarker
}
sc, err = r.preProcessArgument(hostArg)
if err != nil {
return err
}
}

Expand Down
67 changes: 65 additions & 2 deletions internal/runner/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,9 @@ func TestRunner_asnInput_prepareInput(t *testing.T) {
options: options,
hm: hm,
}
// call the prepareInput
err = r.prepareInput()
if isUnauthorizedError(err) {
t.Skip()
t.Skip("skipping: ASN API key not configured")
}
require.Nil(t, err, "failed to prepare input")
expectedOutputFile := "tests/AS14421.txt"
Expand Down Expand Up @@ -141,6 +140,70 @@ 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 func() {
_ = 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",
Expand Down
Loading