Class: MailerToGo::SPF::Resolver

Inherits:
Object
  • Object
show all
Defined in:
lib/mailertogo/spf/resolver.rb

Overview

The DNS seam.

A resolver is anything that responds to #call(name) and returns:

[String, …] — the TXT strings published at that name
[]          — the name exists but has no TXT (or does not exist at all):
            a definitive "nothing here"
nil         — DNS did not answer (timeout, SERVFAIL, refused):
            inconclusive, verdict withheld

That three-way return is the whole contract, and the nil is the important part: a resolver hiccup must never be reported as "this domain does not authorise you". Everything in this gem funnels a nil into an :unknown result rather than a :fail.

A plain lambda satisfies the contract, which is how the test suite runs with no network at all:

zone = { "example.com" => ["v=spf1 include:_spf.mailertogo.net ~all"] }
MailerToGo::SPF.authorize("example.com", resolver: ->(n) { zone.fetch(n, []) })

The default below uses Ruby's stdlib Resolv::DNS, so the gem has no runtime dependencies. If you already speak DNS-over-HTTPS (or hold a resolver pool, or want per-request caching), pass your own — see the README.

Constant Summary collapse

DEFAULT_TIMEOUT =
3

Instance Method Summary collapse

Constructor Details

#initialize(timeout: DEFAULT_TIMEOUT, nameservers: nil) ⇒ Resolver

timeout — seconds per nameserver attempt. nameservers — override the system resolvers, e.g. %w[1.1.1.1 8.8.8.8].



37
38
39
40
# File 'lib/mailertogo/spf/resolver.rb', line 37

def initialize(timeout: DEFAULT_TIMEOUT, nameservers: nil)
  @timeout = timeout
  @nameservers = nameservers
end

Instance Method Details

#call(name) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/mailertogo/spf/resolver.rb', line 42

def call(name)
  query = Record.normalize_name(name)
  return [] if query.empty?

  dns = @nameservers ? ::Resolv::DNS.new(nameserver: Array(@nameservers)) : ::Resolv::DNS.new
  dns.timeouts = @timeout
  begin
    dns.getresources(query, ::Resolv::DNS::Resource::IN::TXT).map { |r| r.strings.join }
  ensure
    dns.close
  end
rescue ::Resolv::ResolvError
  # Resolv collapses NXDOMAIN and "no information" into one error, so this
  # is the conservative reading: the name published nothing. A resolver
  # that can see the rcode (DoH, for instance) should return nil for
  # SERVFAIL and [] only for NXDOMAIN/NODATA — see the README.
  []
rescue StandardError
  # Timeouts (Resolv::ResolvTimeout) and everything else unexpected:
  # inconclusive, never a failure.
  nil
end