Skip to content

Commit

Permalink
BED-564/add-cname-chain-to-spf-tree (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
dmotylev authored Mar 6, 2024
2 parents 1405537 + 3fb9661 commit 26265dd
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 67 deletions.
8 changes: 4 additions & 4 deletions listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ type Listener interface {
NonMatch(qualifier, mechanism, value string, result Result, err error)
Match(qualifier, mechanism, value string, result Result, explanation string, extras *ResponseExtras, err error)
FirstMatch(r Result, err error)
MatchingIP(qualifier, mechanism, value string, fqdn string, ipn net.IPNet, host string, ip net.IP)
// VoidLookup Should only be called after a Directive or CheckHost call, to ensure count is updated to correct
// directive and state is correct
VoidLookup(qualifier, mechanism, value string, fqdn string)
MatchingIP(qualifier, mechanism, value, fqdn string, ipn net.IPNet, host string, ip net.IP)
// LookupExtras should only be called after a Directive or CheckHost call,
// to ensure updates on correct directive and state stay consistent.
LookupExtras(qualifier, mechanism, value, fqdn string, extras *ResponseExtras)
}
33 changes: 15 additions & 18 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,8 @@ func (p *parser) checkHost(ip net.IP, domain, sender string) (r Result, expl str

var txts []string
txts, extras, err = p.resolver.LookupTXTStrict(NormalizeFQDN(domain))
if extras.Void() {
p.fireVoidLookup(nil, domain)
}

p.fireLookupExtras(nil, domain, extras)

switch err {
case nil:
Expand Down Expand Up @@ -377,17 +376,17 @@ func (p *parser) fireMatch(t *token, r Result, explanation string, extras *Respo
p.listener.Match(t.qualifier.String(), t.mechanism.String(), t.value, r, explanation, extras, e)
}

func (p *parser) fireVoidLookup(t *token, fqdn string) {
func (p *parser) fireLookupExtras(t *token, fqdn string, extras *ResponseExtras) {
if p.listener == nil {
return
}

if t == nil {
p.listener.VoidLookup("", "", "", fqdn)
p.listener.LookupExtras("", "", "", fqdn, extras)
return
}

p.listener.VoidLookup(t.qualifier.String(), t.mechanism.String(), t.value, fqdn)
p.listener.LookupExtras(t.qualifier.String(), t.mechanism.String(), t.value, fqdn, extras)
}

func (p *parser) fireFirstMatch(r Result, e error) {
Expand Down Expand Up @@ -536,9 +535,9 @@ func (p *parser) parseA(t *token) (bool, Result, *ResponseExtras, error) {
p.fireMatchingIP(t, fqdn, n, host, p.ip)
return n.Contains(p.ip), nil
})
if extras.Void() {
p.fireVoidLookup(t, fqdn)
}

p.fireLookupExtras(t, fqdn, extras)

if err != nil {
return found, result, nil, NewSpfError(spferr.KindDNS, err, nil)
}
Expand Down Expand Up @@ -576,9 +575,9 @@ func (p *parser) parseMX(t *token) (bool, Result, *ResponseExtras, error) {
p.fireMatchingIP(t, fqdn, n, host, p.ip)
return n.Contains(p.ip), nil
})
if extras.Void() {
p.fireVoidLookup(t, fqdn)
}

p.fireLookupExtras(t, fqdn, extras)

if err != nil {
return true, Permerror, nil, NewSpfError(spferr.KindDNS, err, t)
}
Expand Down Expand Up @@ -673,9 +672,8 @@ func (p *parser) parseExists(t *token) (bool, Result, *ResponseExtras, error) {
result, _ := matchingResult(t.qualifier)

found, extras, err := p.resolver.Exists(resolvedDomain)
if extras.Void() {
p.fireVoidLookup(t, resolvedDomain)
}

p.fireLookupExtras(t, resolvedDomain, extras)

switch err {
case nil:
Expand Down Expand Up @@ -704,9 +702,8 @@ func (p *parser) parsePtr(t *token) (bool, Result, *ResponseExtras, error) {
}

ptrs, extras, err := p.resolver.LookupPTR(p.ip.String())
if extras.Void() {
p.fireVoidLookup(t, fqdn)
}

p.fireLookupExtras(t, fqdn, extras)

switch err {
case nil:
Expand Down
11 changes: 6 additions & 5 deletions printer/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,6 @@ func (p *Printer) Match(qualifier, mechanism, value string, result spf.Result, e
// fmt.Fprintf(p.w, "%sMATCH: %s, %q, %v\n", strings.Repeat(" ", p.c), result, explanation, err)
}

func (p *Printer) VoidLookup(qualifier, mechanism, value string, fqdn string) {
// do nothing
fmt.Fprintf(p.w, "%sVOID: %s\n", strings.Repeat(" ", p.c), fqdn)
}

func (p *Printer) FirstMatch(r spf.Result, err error) {
fmt.Fprintf(p.w, "%sFIRST-MATCH: %s, %v\n", strings.Repeat(" ", p.c), r, err)
}
Expand Down Expand Up @@ -136,3 +131,9 @@ func (p *Printer) MatchIP(name string, matcher spf.IPMatcherFunc) (bool, *spf.Re
func (p *Printer) MatchMX(name string, matcher spf.IPMatcherFunc) (bool, *spf.ResponseExtras, error) {
return p.r.MatchMX(name, matcher)
}

func (p *Printer) LookupExtras(qualifier, mechanism, value, fqdn string, extras *spf.ResponseExtras) {
if extras.Void() {
fmt.Fprintf(p.w, "%sVOID: %s\n", strings.Repeat(" ", p.c), fqdn)
}
}
34 changes: 17 additions & 17 deletions printer/printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func ExamplePrinter() {
// ip4:87.253.232.0/21 (87.253.232.0/21)
// ip4:185.189.236.0/22 (185.189.236.0/22)
// ?all
// = neutral, &{1491000000000 false}, , <nil>
// = neutral, &{1491000000000 false []}, , <nil>
// include:servers.mcsv.net (servers.mcsv.net.)
// CHECK_HOST("0.0.0.0", "servers.mcsv.net.", "aspmx.l.google.com")
// lookup(TXT:strict) servers.mcsv.net.
Expand All @@ -114,7 +114,7 @@ func ExamplePrinter() {
// ip4:198.2.128.0/18 (198.2.128.0/18)
// ip4:148.105.8.0/21 (148.105.8.0/21)
// ?all
// = neutral, &{152000000000 false}, , <nil>
// = neutral, &{152000000000 false []}, , <nil>
// ip4:109.168.127.160/27 (109.168.127.160/27)
// ip4:212.31.252.64/27 (212.31.252.64/27)
// ip4:212.77.68.6 (212.77.68.6)
Expand All @@ -133,7 +133,7 @@ func ExamplePrinter() {
// ip4:109.168.121.57/32 (109.168.121.57/32)
// ip4:109.168.121.58/32 (109.168.121.58/32)
// -all
// = fail, &{269000000000 false}, , <nil>
// = fail, &{269000000000 false []}, , <nil>
// CHECK_HOST("0.0.0.0", "ptr.test.redsift.io.", "aspmx.l.google.com")
// lookup(TXT:strict) ptr.test.redsift.io.
// SPF: v=spf1 ptr ~all
Expand All @@ -142,7 +142,7 @@ func ExamplePrinter() {
// lookup(PTR) 0.0.0.0
// VOID: ptr.test.redsift.io.
// ~all
// = softfail, &{299000000000 false}, , <nil>
// = softfail, &{299000000000 false []}, , <nil>
// ## of lookups: 15
}

Expand Down Expand Up @@ -210,9 +210,9 @@ func ExamplePrinter_ipv6nil() {
// lookup(a:web.q4press.com.) web.q4press.com. -> (52.23.113.139/32 has? 0.0.0.0) = false
// lookup(a:web.q4press.com.) web.q4press.com. -> (54.177.118.13/32 has? 0.0.0.0) = false
// -all
// = fail, &{3303000000000 false}, , <nil>
// = fail, &{3303000000000 false []}, , <nil>
// ~all
// = softfail, &{59000000000 false}, , <nil>
// = softfail, &{59000000000 false []}, , <nil>
//
}

Expand Down Expand Up @@ -254,12 +254,12 @@ func ExamplePrinter_voids() {
// ip4:80.194.146.205 (80.194.146.205)
// -all
// FIRST-MATCH: fail, <nil>
// = 8, &{60000000000 false}, , result is unreliable with IgnoreMatches option enabled
// = 8, &{60000000000 false []}, , result is unreliable with IgnoreMatches option enabled
// include:err008.3.spf.qa.redsift.tech (err008.3.spf.qa.redsift.tech.)
// CHECK_HOST("0.0.0.0", "err008.3.spf.qa.redsift.tech.", "redsift.io")
// lookup(TXT:strict) err008.3.spf.qa.redsift.tech.
// VOID: err008.3.spf.qa.redsift.tech.
// = none, &{0 true}, , permanent DNS error
// = none, &{0 true []}, , permanent DNS error
// include:err008.4.spf.qa.redsift.tech (err008.4.spf.qa.redsift.tech.)
// CHECK_HOST("0.0.0.0", "err008.4.spf.qa.redsift.tech.", "redsift.io")
// lookup(TXT:strict) err008.4.spf.qa.redsift.tech.
Expand All @@ -269,18 +269,18 @@ func ExamplePrinter_voids() {
// CHECK_HOST("0.0.0.0", "err008.5.spf.qa.redsift.tech.", "redsift.io")
// lookup(TXT:strict) err008.5.spf.qa.redsift.tech.
// VOID: err008.5.spf.qa.redsift.tech.
// = none, &{0 true}, , SPF record not found
// = none, &{0 true []}, , SPF record not found
// include:err008.6.spf.qa.redsift.tech (err008.6.spf.qa.redsift.tech.)
// CHECK_HOST("0.0.0.0", "err008.6.spf.qa.redsift.tech.", "redsift.io")
// lookup(TXT:strict) err008.6.spf.qa.redsift.tech.
// VOID: err008.6.spf.qa.redsift.tech.
// = none, &{0 true}, , permanent DNS error
// = none, &{0 true []}, , permanent DNS error
// -all
// = 8, &{60000000000 false}, , result is unreliable with IgnoreMatches option enabled
// = 8, &{60000000000 false []}, , result is unreliable with IgnoreMatches option enabled
// -all
// = 8, &{60000000000 false}, , result is unreliable with IgnoreMatches option enabled
// = 8, &{60000000000 false []}, , result is unreliable with IgnoreMatches option enabled
// -all
// = 8, &{60000000000 false}, , result is unreliable with IgnoreMatches option enabled
// = 8, &{60000000000 false []}, , result is unreliable with IgnoreMatches option enabled
// ## of lookups: 6

}
Expand Down Expand Up @@ -374,7 +374,7 @@ func ExamplePrinter_ignoreMatches() {
// ip4:185.189.236.0/22 (185.189.236.0/22)
// ?all
// FIRST-MATCH: neutral, <nil>
// = 8, &{1491000000000 false}, , result is unreliable with IgnoreMatches option enabled
// = 8, &{1491000000000 false []}, , result is unreliable with IgnoreMatches option enabled
// include:servers.mcsv.net (servers.mcsv.net.)
// CHECK_HOST("0.0.0.0", "servers.mcsv.net.", "aspmx.l.google.com")
// lookup(TXT:strict) servers.mcsv.net.
Expand All @@ -384,7 +384,7 @@ func ExamplePrinter_ignoreMatches() {
// ip4:198.2.128.0/18 (198.2.128.0/18)
// ip4:148.105.8.0/21 (148.105.8.0/21)
// ?all
// = 8, &{152000000000 false}, , result is unreliable with IgnoreMatches option enabled
// = 8, &{152000000000 false []}, , result is unreliable with IgnoreMatches option enabled
// ip4:109.168.127.160/27 (109.168.127.160/27)
// ip4:212.31.252.64/27 (212.31.252.64/27)
// ip4:212.77.68.6 (212.77.68.6)
Expand All @@ -403,7 +403,7 @@ func ExamplePrinter_ignoreMatches() {
// ip4:109.168.121.57/32 (109.168.121.57/32)
// ip4:109.168.121.58/32 (109.168.121.58/32)
// -all
// = 8, &{269000000000 false}, , result is unreliable with IgnoreMatches option enabled
// = 8, &{269000000000 false []}, , result is unreliable with IgnoreMatches option enabled
// CHECK_HOST("0.0.0.0", "ptr.test.redsift.io.", "aspmx.l.google.com")
// lookup(TXT:strict) ptr.test.redsift.io.
// SPF: v=spf1 ptr ~all
Expand All @@ -413,6 +413,6 @@ func ExamplePrinter_ignoreMatches() {
// VOID: ptr.test.redsift.io.
// ~all
// FIRST-MATCH: softfail, <nil>
// = 8, &{299000000000 false}, , result is unreliable with IgnoreMatches option enabled
// = 8, &{299000000000 false []}, , result is unreliable with IgnoreMatches option enabled
// ## of lookups: 15
}
24 changes: 6 additions & 18 deletions resolver_miekg.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,7 @@ func (r *miekgDNSResolver) LookupTXT(name string) ([]string, *ResponseExtras, er
minTTL = 0
}

extras := NewResponseExtras(
time.Duration(minTTL)*time.Second,
(len(res.Answer) == 0 && res.Rcode == dns.RcodeSuccess) || res.Rcode == dns.RcodeNameError,
)
extras := NewResponseExtras(time.Duration(minTTL)*time.Second, (len(res.Answer) == 0 && res.Rcode == dns.RcodeSuccess) || res.Rcode == dns.RcodeNameError, nil)

return txts, extras, nil
}
Expand All @@ -216,7 +213,7 @@ func (r *miekgDNSResolver) LookupTXTStrict(name string) ([]string, *ResponseExtr

if res.Rcode == dns.RcodeNameError {
// Mark it as a void lookup as we got NXDomain
return nil, NewResponseExtras(0, true), ErrDNSPermerror
return nil, NewResponseExtras(0, true, nil), ErrDNSPermerror
}

var minTTL uint32 = math.MaxUint32
Expand All @@ -235,10 +232,7 @@ func (r *miekgDNSResolver) LookupTXTStrict(name string) ([]string, *ResponseExtr
minTTL = 0
}

extras := NewResponseExtras(
time.Duration(minTTL)*time.Second,
len(res.Answer) == 0 && res.Rcode == dns.RcodeSuccess,
)
extras := NewResponseExtras(time.Duration(minTTL)*time.Second, len(res.Answer) == 0 && res.Rcode == dns.RcodeSuccess, nil)

return txts, extras, nil
}
Expand Down Expand Up @@ -271,10 +265,7 @@ func (r *miekgDNSResolver) Exists(name string) (bool, *ResponseExtras, error) {
minTTL = 0
}

extras := NewResponseExtras(
time.Duration(minTTL)*time.Second,
(len(res.Answer) == 0 && res.Rcode == dns.RcodeSuccess) || res.Rcode == dns.RcodeNameError,
)
extras := NewResponseExtras(time.Duration(minTTL)*time.Second, (len(res.Answer) == 0 && res.Rcode == dns.RcodeSuccess) || res.Rcode == dns.RcodeNameError, nil)

return len(res.Answer) > 0, extras, nil
}
Expand All @@ -298,7 +289,7 @@ func matchIP(rrs []dns.RR, matcher IPMatcherFunc, name string) (bool, *ResponseE
}

if m, e := matcher(ip, name); m || e != nil {
return m, NewResponseExtras(time.Duration(ttl)*time.Second, false), e
return m, NewResponseExtras(time.Duration(ttl)*time.Second, false, nil), e
}
}
return false, nil, nil
Expand Down Expand Up @@ -440,10 +431,7 @@ func (r *miekgDNSResolver) LookupPTR(name string) ([]string, *ResponseExtras, er
minTTL = 0
}

extras := NewResponseExtras(
time.Duration(minTTL)*time.Second,
(len(res.Answer) == 0 && res.Rcode == dns.RcodeSuccess) || res.Rcode == dns.RcodeNameError,
)
extras := NewResponseExtras(time.Duration(minTTL)*time.Second, (len(res.Answer) == 0 && res.Rcode == dns.RcodeSuccess) || res.Rcode == dns.RcodeNameError, nil)

return ptrs, extras, nil
}
Expand Down
22 changes: 17 additions & 5 deletions spf.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,23 @@ type IPMatcherFunc func(ip net.IP, name string) (bool, error)

// ResponseExtras contains additional information returned alongside DNS query results.
type ResponseExtras struct {
ttl time.Duration // Minimum TTL of the DNS response
void bool // Indicates if the response is a result of a DNS void lookup.

ttl time.Duration // Minimum TTL of the DNS response
// Indicates if the response is a result of a DNS void lookup.
// A DNS void lookup, as defined in Section 4.6.4 of RFC 7208 (https://datatracker.ietf.org/doc/html/rfc7208#section-4.6.4),
// is a query for a domain that is intentionally configured to have no associated DNS records,
// such as an explicit configuration for a "blackhole" or an intentionally nonexistent domain.
// This type of query typically returns a response with no relevant DNS records (e.g., NXDOMAIN),
// and the 'Void' field in this struct is set to 'true' to indicate that the response resulted from such a lookup.
void bool
cnameChain []string
}

func NewResponseExtras(ttl time.Duration, void bool) *ResponseExtras {
return &ResponseExtras{ttl, void}
func NewResponseExtras(ttl time.Duration, void bool, cnameChain []string) *ResponseExtras {
return &ResponseExtras{
ttl: ttl,
void: void,
cnameChain: cnameChain,
}
}

func (x *ResponseExtras) TTL() time.Duration {
Expand All @@ -93,6 +98,13 @@ func (x *ResponseExtras) Void() bool {
return x != nil && x.void
}

func (x *ResponseExtras) CNAMEChain() []string {
if x == nil {
return nil
}
return x.cnameChain
}

// Resolver provides an abstraction for DNS layer operations.
type Resolver interface {
// LookupTXT returns the DNS TXT records for the given domain name,
Expand Down

0 comments on commit 26265dd

Please sign in to comment.