-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Expand file tree
/
Copy pathrunner.go
More file actions
179 lines (154 loc) · 5.83 KB
/
runner.go
File metadata and controls
179 lines (154 loc) · 5.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package runner
import (
"bufio"
"context"
"io"
"math"
"os"
"path"
"regexp"
"strconv"
"strings"
"time"
"github.com/projectdiscovery/gologger"
contextutil "github.com/projectdiscovery/utils/context"
fileutil "github.com/projectdiscovery/utils/file"
mapsutil "github.com/projectdiscovery/utils/maps"
"github.com/projectdiscovery/subfinder/v2/pkg/passive"
"github.com/projectdiscovery/subfinder/v2/pkg/resolve"
"github.com/projectdiscovery/subfinder/v2/pkg/subscraping"
)
// Runner is an instance of the subdomain enumeration
// client used to orchestrate the whole process.
type Runner struct {
options *Options
passiveAgent *passive.Agent
resolverClient *resolve.Resolver
rateLimit *subscraping.CustomRateLimit
}
// NewRunner creates a new runner struct instance by parsing
// the configuration options, configuring sources, reading lists
// and setting up loggers, etc.
func NewRunner(options *Options) (*Runner, error) {
options.ConfigureOutput()
runner := &Runner{options: options}
// Check if the application loading with any provider configuration, then take it
// Otherwise load the default provider config
if fileutil.FileExists(options.ProviderConfig) {
gologger.Info().Msgf("Loading provider config from %s", options.ProviderConfig)
options.loadProvidersFrom(options.ProviderConfig)
} else {
gologger.Info().Msgf("Loading provider config from the default location: %s", defaultProviderConfigLocation)
options.loadProvidersFrom(defaultProviderConfigLocation)
}
// Initialize the passive subdomain enumeration engine
runner.initializePassiveEngine()
// Initialize the subdomain resolver
err := runner.initializeResolver()
if err != nil {
return nil, err
}
// Initialize the custom rate limit
runner.rateLimit = &subscraping.CustomRateLimit{
Custom: mapsutil.SyncLockMap[string, uint]{
Map: make(map[string]uint),
},
}
for source, sourceRateLimit := range options.RateLimits.AsMap() {
if sourceRateLimit.MaxCount > 0 && sourceRateLimit.MaxCount <= math.MaxUint {
_ = runner.rateLimit.Custom.Set(source, sourceRateLimit.MaxCount)
}
}
return runner, nil
}
// RunEnumeration wraps RunEnumerationWithCtx with an empty context
func (r *Runner) RunEnumeration() error {
ctx, _ := contextutil.WithValues(context.Background(), contextutil.ContextArg("All"), contextutil.ContextArg(strconv.FormatBool(r.options.All)))
return r.RunEnumerationWithCtx(ctx)
}
// RunEnumerationWithCtx runs the subdomain enumeration flow on the targets specified
func (r *Runner) RunEnumerationWithCtx(ctx context.Context) error {
timeout := time.Duration(r.options.Timeout) * time.Second
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
outputs := []io.Writer{r.options.Output}
if len(r.options.Domain) > 0 {
domainsReader := strings.NewReader(strings.Join(r.options.Domain, "\n"))
return r.EnumerateMultipleDomainsWithCtx(ctx, domainsReader, outputs)
}
// If we have multiple domains as input,
if r.options.DomainsFile != "" {
f, err := os.Open(r.options.DomainsFile)
if err != nil {
return err
}
err = r.EnumerateMultipleDomainsWithCtx(ctx, f, outputs)
if closeErr := f.Close(); closeErr != nil {
gologger.Error().Msgf("Error closing file %s: %s", r.options.DomainsFile, closeErr)
}
return err
}
// If we have STDIN input, treat it as multiple domains
if r.options.Stdin {
return r.EnumerateMultipleDomainsWithCtx(ctx, os.Stdin, outputs)
}
return nil
}
// EnumerateMultipleDomains wraps EnumerateMultipleDomainsWithCtx with an empty context
func (r *Runner) EnumerateMultipleDomains(reader io.Reader, writers []io.Writer) error {
ctx, _ := contextutil.WithValues(context.Background(), contextutil.ContextArg("All"), contextutil.ContextArg(strconv.FormatBool(r.options.All)))
return r.EnumerateMultipleDomainsWithCtx(ctx, reader, writers)
}
// EnumerateMultipleDomainsWithCtx enumerates subdomains for multiple domains
// We keep enumerating subdomains for a given domain until we reach an error
func (r *Runner) EnumerateMultipleDomainsWithCtx(ctx context.Context, reader io.Reader, writers []io.Writer) error {
var err error
scanner := bufio.NewScanner(reader)
ip, _ := regexp.Compile(`^([0-9\.]+$)`)
for scanner.Scan() {
domain := preprocessDomain(scanner.Text())
domain = replacer.Replace(domain)
if domain == "" || (r.options.ExcludeIps && ip.MatchString(domain)) {
continue
}
var file *os.File
// If the user has specified an output file, use that output file instead
// of creating a new output file for each domain. Else create a new file
// for each domain in the directory.
if r.options.OutputFile != "" {
outputWriter := NewOutputWriter(r.options.JSON)
file, err = outputWriter.createFile(r.options.OutputFile, true)
if err != nil {
gologger.Error().Msgf("Could not create file %s for %s: %s\n", r.options.OutputFile, r.options.Domain, err)
return err
}
_, err = r.EnumerateSingleDomainWithCtx(ctx, domain, append(writers, file))
if closeErr := file.Close(); closeErr != nil {
gologger.Error().Msgf("Error closing file %s: %s", r.options.OutputFile, closeErr)
}
} else if r.options.OutputDirectory != "" {
outputFile := path.Join(r.options.OutputDirectory, domain)
if r.options.JSON {
outputFile += ".json"
} else {
outputFile += ".txt"
}
outputWriter := NewOutputWriter(r.options.JSON)
file, err = outputWriter.createFile(outputFile, false)
if err != nil {
gologger.Error().Msgf("Could not create file %s for %s: %s\n", r.options.OutputFile, r.options.Domain, err)
return err
}
_, err = r.EnumerateSingleDomainWithCtx(ctx, domain, append(writers, file))
if closeErr := file.Close(); closeErr != nil {
gologger.Error().Msgf("Error closing file %s: %s", outputFile, closeErr)
}
} else {
_, err = r.EnumerateSingleDomainWithCtx(ctx, domain, writers)
}
if err != nil {
return err
}
}
return nil
}