Module: MailerToGo::SPF

Defined in:
lib/mailertogo/spf.rb,
lib/mailertogo/spf/plan.rb,
lib/mailertogo/spf/record.rb,
lib/mailertogo/spf/result.rb,
lib/mailertogo/spf/sender.rb,
lib/mailertogo/spf/version.rb,
lib/mailertogo/spf/resolver.rb,
lib/mailertogo/spf/merge_plan.rb,
lib/mailertogo/spf/authorization.rb

Overview

An SPF engine that reads a record the way a receiving MTA does: it resolves the include chain, stops where the receiver stops, and counts DNS lookups against the RFC 7208 §4.6.4 cap.

Two questions, two entry points:

MailerToGo::SPF.authorize("example.com")
→ does this domain's published SPF authorize me?

MailerToGo::SPF.merge_plan("example.com")
→ what should I tell them to publish, given what is already there?

Both take include: (the mechanism you want authorized) and aliases: (other names that mean the same sender). Both default to MailerToGo's own names, so the zero-argument form is the useful one for MailerToGo customers and one keyword makes it work for anybody else:

MailerToGo::SPF.authorize("example.com", include: "spf.example.net")

Defined Under Namespace

Modules: Record Classes: Authorization, CachingResolver, MergePlan, Plan, Resolver, Result, Sender

Constant Summary collapse

DEFAULT_INCLUDE =

MailerToGo publishes an outer alias and a leaf. Customers are told to include the leaf, but either one authorizes us, so both count as "me".

"_spf.mailertogo.net"
DEFAULT_ALIASES =
["mailertogo.net"].freeze
VERSION =
"0.1.0"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.aliasesObject



53
54
55
# File 'lib/mailertogo/spf.rb', line 53

def aliases
  defined?(@aliases) && @aliases ? @aliases : DEFAULT_ALIASES
end

.include=(value) ⇒ Object (writeonly)

Sets the attribute include

Parameters:

  • value

    the value to set the attribute include to.



47
48
49
# File 'lib/mailertogo/spf.rb', line 47

def include=(value)
  @include = value
end

.loggerObject

Process-wide defaults. Everything here can also be passed per call.

MailerToGo::SPF.configure do |c|
c.include  = "spf.example.net"
c.aliases  = ["example.net"]
c.resolver = MailerToGo::SPF::CachingResolver.new(MailerToGo::SPF::Resolver.new)
c.logger   = Logger.new($stdout)
end


46
47
48
# File 'lib/mailertogo/spf.rb', line 46

def logger
  @logger
end

.resolverObject

Resolving is I/O, so the default is built once and shared. Pass your own (any object responding to #call(name)) to change how DNS happens.



59
60
61
# File 'lib/mailertogo/spf.rb', line 59

def resolver
  @resolver ||= Resolver.new
end

Class Method Details

.authorize(hostname, include: nil, aliases: nil, resolver: nil, sender: nil, published: nil, logger: nil) ⇒ Object

Does 's published SPF authorize the sender? Returns a Result (see result.rb) — never raises for a DNS failure.

published: evaluates a supplied record as if it were published at , which is how you price a record that does not exist yet.

Raises:

  • (ArgumentError)


73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/mailertogo/spf.rb', line 73

def authorize(hostname, include: nil, aliases: nil, resolver: nil,
              sender: nil, published: nil, logger: nil)
  sender ||= sender_for(include || include_name, aliases)
  raise ArgumentError, "no include name given or configured" if sender.nil?

  Authorization.call(
    hostname,
    sender: sender,
    resolver: resolver || self.resolver,
    published: published,
    logger: logger || self.logger
  )
end

.configure {|_self| ... } ⇒ Object

Yields:

  • (_self)

Yield Parameters:



63
64
65
66
# File 'lib/mailertogo/spf.rb', line 63

def configure
  yield self
  self
end

.include_nameObject



49
50
51
# File 'lib/mailertogo/spf.rb', line 49

def include_name
  defined?(@include) && @include ? @include : DEFAULT_INCLUDE
end

.merge_plan(name, record: nil, include: nil, aliases: nil, resolver: nil, sender: nil, authorization: nil, logger: nil) ⇒ Object

What should this domain publish, given what is already at that name? Returns a Plan (see plan.rb).

record: the standalone record you would otherwise hand them; defaults to the sender's own v=spf1 include:… ~all.



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/mailertogo/spf.rb', line 92

def merge_plan(name, record: nil, include: nil, aliases: nil, resolver: nil,
               sender: nil, authorization: nil, logger: nil)
  # A record with no include mechanism gives us no identity to merge in —
  # hand back a null plan rather than inventing one.
  sender ||= sender_for(include || (record.nil? ? include_name : Record.include_target(record)), aliases)
  return Plan.none(name: name, record: record) if sender.nil?

  MergePlan.call(
    name,
    record: record || sender.record,
    sender: sender,
    resolver: resolver || self.resolver,
    authorization: authorization,
    logger: logger || self.logger
  )
end

.sender(include: nil, aliases: nil) ⇒ Object

The names that mean "me". Public because a caller that asks both questions about the same sender should build it once.



111
112
113
# File 'lib/mailertogo/spf.rb', line 111

def sender(include: nil, aliases: nil)
  sender_for(include || include_name, aliases)
end