Skip to content

Commit 54943c0

Browse files
committed
Fix API rate limit bug
Add `onmicrosoft.com` DKIM selector fallback (#35)
1 parent 7ab3f51 commit 54943c0

6 files changed

Lines changed: 33 additions & 16 deletions

File tree

cmd/dss/scan.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func newScanCMD() *cobra.Command {
1616
Example: " dss scan <STDIN>\n dss scan globalcyberalliance.org gcaaide.org google.com\n dss scan -z < zonefile",
1717
Short: "Scan DNS records for one or multiple domains.",
1818
Long: "Scan DNS records for one or multiple domains.\nBy default, the command will listen on STDIN, allowing you to type or pipe multiple domains.",
19-
Run: func(_ *cobra.Command, args []string) {
19+
Run: func(cmd *cobra.Command, args []string) {
2020
sc, err := scanner.New(log, timeout, getScannerOpts()...)
2121
if err != nil {
2222
log.Fatal().Err(err).Msg("An unexpected error occurred.")
@@ -56,7 +56,7 @@ func newScanCMD() *cobra.Command {
5656
for lineScanner.Scan() {
5757
domain := lineScanner.Text()
5858

59-
results, err = sc.Scan(domain)
59+
results, err = sc.Scan(cmd.Context(), domain)
6060
if err != nil {
6161
log.Fatal().Err(err).Msg("An unexpected error occurred.")
6262
}
@@ -74,7 +74,7 @@ func newScanCMD() *cobra.Command {
7474
printToConsole(resultsWithAdvice)
7575
}
7676
} else {
77-
results, err = sc.Scan(args...)
77+
results, err = sc.Scan(cmd.Context(), args...)
7878
if err != nil {
7979
log.Fatal().Err(err).Msg("An unexpected error occurred.")
8080
}

pkg/http/scan.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func (s *Server) registerScanRoutes() {
2626
Method: http.MethodGet,
2727
Path: s.apiPath + "/scan/{domain}",
2828
Tags: []string{"Scan Domains"},
29-
}, func(_ context.Context, input *ScanSingleDomainRequest) (*ScanSingleDomainResponse, error) {
29+
}, func(ctx context.Context, input *ScanSingleDomainRequest) (*ScanSingleDomainResponse, error) {
3030
resp := ScanSingleDomainResponse{}
3131

3232
if len(input.DKIMSelectors) > 0 {
@@ -35,7 +35,7 @@ func (s *Server) registerScanRoutes() {
3535
}
3636
}
3737

38-
results, err := s.Scanner.Scan(input.Domain)
38+
results, err := s.Scanner.Scan(ctx, input.Domain)
3939
if err != nil {
4040
return nil, huma.Error500InternalServerError(err.Error())
4141
}
@@ -80,10 +80,10 @@ func (s *Server) registerScanRoutes() {
8080
Method: http.MethodPost,
8181
Path: s.apiPath + "/scan",
8282
Tags: []string{"Scan Domains"},
83-
}, func(_ context.Context, input *ScanBulkDomainsRequest) (*ScanBulkDomainResponse, error) {
83+
}, func(ctx context.Context, input *ScanBulkDomainsRequest) (*ScanBulkDomainResponse, error) {
8484
resp := ScanBulkDomainResponse{}
8585

86-
results, err := s.Scanner.Scan(input.Body.Domains...)
86+
results, err := s.Scanner.Scan(ctx, input.Body.Domains...)
8787
if err != nil {
8888
return nil, huma.Error500InternalServerError(err.Error())
8989
}

pkg/http/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ func NewServer(logger zerolog.Logger, timeout time.Duration, rateLimit int, vers
6565
MaxAge: 300, // The maximum value not ignored by any of the major browsers.
6666
}))
6767
mux.Use(httprate.Limit(rateLimit, 3*time.Second,
68+
httprate.WithKeyFuncs(httprate.KeyByRealIP),
6869
httprate.WithLimitHandler(func(w http.ResponseWriter, _ *http.Request) {
6970
response, err := json.Marshal(huma.Error429TooManyRequests("try again later"))
7071
if err != nil {

pkg/mail/server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package mail
22

33
import (
4+
"context"
45
"fmt"
56
htmlTmpl "html/template"
67
textTmpl "text/template"
@@ -100,7 +101,7 @@ func (s *Server) handler() error {
100101
}
101102
}
102103

103-
results, err := s.Scanner.Scan(domainList...)
104+
results, err := s.Scanner.Scan(context.Background(), domainList...)
104105
if err != nil {
105106
s.logger.Error().Err(err).Msg("An error occurred while scanning domains")
106107
continue

pkg/scanner/requests.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ var (
1919
DMARCPrefix = regexp.MustCompile(`^v\s*=\s*DMARC1`) // Matches v=DMARC1 with whitespace (RFC7489).
2020
SPFPrefix = regexp.MustCompile(`^v=(?i)spf1`)
2121

22-
// knownDkimSelectors is a list of known DKIM selectors.
23-
knownDkimSelectors = []string{
22+
// knownDKIMSelectors is a list of known DKIM selectors.
23+
knownDKIMSelectors = []string{
2424
"x", // Generic
2525
"google", // Google
2626
"selector1", // Microsoft
@@ -94,7 +94,7 @@ func (s *Scanner) getDNSAnswers(domain string, recordType uint16) ([]dns.RR, err
9494
}
9595

9696
if in.Rcode != dns.RcodeSuccess {
97-
// disregard NXDOMAIN errors
97+
// Disregard NXDOMAIN errors.
9898
if in.Rcode == dns.RcodeNameError {
9999
return nil, nil
100100
}
@@ -140,7 +140,11 @@ func (s *Scanner) getTypeBIMI(domain string) (string, error) {
140140
// getTypeDKIM queries the DNS server for DKIM records of a domain.
141141
// It returns a string (DKIM record) and an error if any occurred.
142142
func (s *Scanner) getTypeDKIM(domain string) (string, error) {
143-
selectors := append(s.dkimSelectors, knownDkimSelectors...)
143+
selectors := append(s.dkimSelectors, knownDKIMSelectors...)
144+
145+
// Some Microsoft domains (e.g. "onmicrosoft.com") use dynamic selectors.
146+
dashedDomain := strings.ReplaceAll(strings.ToLower(domain), ".", "-")
147+
selectors = append(selectors, "selector1-"+dashedDomain, "selector2-"+dashedDomain)
144148

145149
for _, selector := range selectors {
146150
records, err := s.getDNSRecords(selector+"._domainkey."+domain, dns.TypeTXT)
@@ -150,7 +154,7 @@ func (s *Scanner) getTypeDKIM(domain string) (string, error) {
150154

151155
for index, record := range records {
152156
if strings.HasPrefix(record, DKIMPrefix) {
153-
// TXT records can be split across multiple strings, so we need to join them
157+
// TXT records can be split across multiple strings, we need to join them.
154158
return strings.Join(records[index:], ""), nil
155159
}
156160
}
@@ -173,7 +177,7 @@ func (s *Scanner) getTypeDMARC(domain string) (string, error) {
173177

174178
for index, record := range records {
175179
if DMARCPrefix.MatchString(record) {
176-
// TXT records can be split across multiple strings, so we need to join them
180+
// TXT records can be split across multiple strings, we need to join them.
177181
return strings.Join(records[index:], ""), nil
178182
}
179183
}

pkg/scanner/scanner.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package scanner
22

33
import (
4+
"context"
45
"fmt"
56
"io"
67
"runtime"
@@ -113,7 +114,7 @@ func New(logger zerolog.Logger, timeout time.Duration, opts ...Option) (*Scanner
113114
}
114115

115116
// Scan scans a list of domains and returns the results.
116-
func (s *Scanner) Scan(domains ...string) ([]*Result, error) {
117+
func (s *Scanner) Scan(ctx context.Context, domains ...string) ([]*Result, error) {
117118
if s.pool == nil {
118119
return nil, errors.New("scanner is closed")
119120
}
@@ -140,6 +141,12 @@ func (s *Scanner) Scan(domains ...string) ([]*Result, error) {
140141
wg.Done()
141142
}()
142143

144+
select {
145+
case <-ctx.Done():
146+
return
147+
default:
148+
}
149+
143150
var err error
144151
result := &Result{
145152
Domain: domainToScan,
@@ -247,6 +254,10 @@ func (s *Scanner) Scan(domains ...string) ([]*Result, error) {
247254

248255
wg.Wait()
249256

257+
if ctx.Err() != nil {
258+
return nil, ctx.Err()
259+
}
260+
250261
return results, nil
251262
}
252263

@@ -274,7 +285,7 @@ func (s *Scanner) ScanZone(zone io.Reader) ([]*Result, error) {
274285
domains = append(domains, domain)
275286
}
276287

277-
return s.Scan(domains...)
288+
return s.Scan(context.Background(), domains...)
278289
}
279290

280291
// Close closes the scanner.

0 commit comments

Comments
 (0)