diff --git a/pkg/passive/sources.go b/pkg/passive/sources.go index af7d5e6e8..e6f5538a6 100644 --- a/pkg/passive/sources.go +++ b/pkg/passive/sources.go @@ -25,6 +25,7 @@ import ( "github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/digitorus" "github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/dnsdb" "github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/dnsdumpster" + "github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/domainsproject" "github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/dnsrepo" "github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/driftnet" "github.com/projectdiscovery/subfinder/v2/pkg/subscraping/sources/facebook" @@ -71,6 +72,7 @@ var AllSources = [...]subscraping.Source{ &digitorus.Source{}, &dnsdb.Source{}, &dnsdumpster.Source{}, + &domainsproject.Source{}, &dnsrepo.Source{}, &driftnet.Source{}, &fofa.Source{}, diff --git a/pkg/passive/sources_test.go b/pkg/passive/sources_test.go index abcad1471..94c588160 100644 --- a/pkg/passive/sources_test.go +++ b/pkg/passive/sources_test.go @@ -26,6 +26,7 @@ var ( "dnsdumpster", "dnsdb", "dnsrepo", + "domainsproject", "driftnet", "fofa", "fullhunt", @@ -73,6 +74,7 @@ var ( "crtsh", "digitorus", "dnsdumpster", + "domainsproject", "dnsrepo", "driftnet", "fofa", diff --git a/pkg/subscraping/sources/domainsproject/domainsproject.go b/pkg/subscraping/sources/domainsproject/domainsproject.go new file mode 100644 index 000000000..4ae8d00dc --- /dev/null +++ b/pkg/subscraping/sources/domainsproject/domainsproject.go @@ -0,0 +1,133 @@ +// Package domainsproject logic +package domainsproject + +import ( + "context" + "fmt" + "strings" + "time" + + jsoniter "github.com/json-iterator/go" + + "github.com/projectdiscovery/subfinder/v2/pkg/subscraping" +) + +// Source is the passive scraping agent +type Source struct { + apiKeys []apiKey + timeTaken time.Duration + errors int + results int + skipped bool +} + +type apiKey struct { + username string + password string +} + +type domainsProjectResponse struct { + Domains []string `json:"domains"` + Error string `json:"error"` +} + +// Run function returns all subdomains found with the service +func (s *Source) Run(ctx context.Context, domain string, session *subscraping.Session) <-chan subscraping.Result { + results := make(chan subscraping.Result) + s.errors = 0 + s.results = 0 + + go func() { + defer func(startTime time.Time) { + s.timeTaken = time.Since(startTime) + close(results) + }(time.Now()) + + randomApiKey := subscraping.PickRandom(s.apiKeys, s.Name()) + if randomApiKey.username == "" || randomApiKey.password == "" { + s.skipped = true + return + } + + searchURL := fmt.Sprintf("https://api.domainsproject.org/api/tld/search?domain=%s", domain) + resp, err := session.HTTPRequest( + ctx, + "GET", + searchURL, + "", + nil, + nil, + subscraping.BasicAuth{Username: randomApiKey.username, Password: randomApiKey.password}, + ) + if err != nil { + results <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: err} + s.errors++ + session.DiscardHTTPResponse(resp) + return + } + + defer func() { + if err := resp.Body.Close(); err != nil { + results <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: err} + s.errors++ + } + }() + + var response domainsProjectResponse + err = jsoniter.NewDecoder(resp.Body).Decode(&response) + if err != nil { + results <- subscraping.Result{Source: s.Name(), Type: subscraping.Error, Error: err} + s.errors++ + return + } + + if response.Error != "" { + results <- subscraping.Result{ + Source: s.Name(), Type: subscraping.Error, Error: fmt.Errorf("%v", response.Error), + } + s.errors++ + return + } + + for _, subdomain := range response.Domains { + if !strings.HasPrefix(subdomain, ".") { + results <- subscraping.Result{Source: s.Name(), Type: subscraping.Subdomain, Value: subdomain} + s.results++ + } + } + }() + + return results +} + +// Name returns the name of the source +func (s *Source) Name() string { + return "domainsproject" +} + +func (s *Source) IsDefault() bool { + return true +} + +func (s *Source) HasRecursiveSupport() bool { + return false +} + +func (s *Source) NeedsKey() bool { + return true +} + +func (s *Source) AddApiKeys(keys []string) { + s.apiKeys = subscraping.CreateApiKeys(keys, func(k, v string) apiKey { + return apiKey{k, v} + }) +} + +func (s *Source) Statistics() subscraping.Statistics { + return subscraping.Statistics{ + Errors: s.errors, + Results: s.results, + TimeTaken: s.timeTaken, + Skipped: s.skipped, + } +}