spf-ruby icon indicating copy to clipboard operation
spf-ruby copied to clipboard

TypeError with nested SPF record with the domain macro

Open Hirosvk opened this issue 1 year ago • 1 comments

Hello team,

I would like to report a bug where a TypeError is raised when nested SPF record contains the %{d} (aka ) macro.

Spec to reproduce:

require File.expand_path(File.dirname(__FILE__) + '/spec_helper')

describe 'SPF::Server' do
  describe '#process' do
    let(:dns_resolver) do
      MockDnsResolver.new(
        'mail1.example.com' => {
          Resolv::DNS::Resource::IN::TXT => 'v=spf1 include:mail2.example.com ~all'
        },
        'mail2.example.com' => {
          Resolv::DNS::Resource::IN::TXT => 'v=spf1 include:mail.%{d}.abc.xyz ~all'
        },
        'mail.mail2.example.com.abc.xyz' => {
          Resolv::DNS::Resource::IN::TXT => 'v=spf1 ip4:192.168.0.0/4'
        }
      )
    end
    let(:server) do
      SPF::Server.new(dns_resolver: dns_resolver)
    end
    let(:request) do
      SPF::Request.new(
        scope:         'helo',
        identity:      'mail1.example.com',
        ip_address:    '192.168.0.1',
        helo_identity: 'mta.example.com',
      )
    end
    subject { server.process(request) }

    it 'processes request' do
      expect { subject }.to raise_error TypeError
    end
  end
end

class MockDnsResolver
  def initialize(mock_responses = {})
    @mock_responses = mock_responses
  end

  def getresources(name, typeclass)
    [Resolv::DNS::Resource::IN::TXT.new(@mock_responses[name][typeclass])]
  end
end

(We had a real-life scenario with our customer's SPF record, which I cannot share in a public space.)

Explanation: The SPF record for mail1.example.com includes include:mail2.example.com. The record for mail2.example.com includes the macro include:mail.%{d}.abc.xyz which is responsible for the line where the error comes from. The error occurs only when it's called as a nested record; in other words, if you process the request for the mail2.example.com domain directly, the request was processed without error.

When the gem processes a nested record, it creates a new request with the authority_domain as an instance of SPF::MacroString (here and here). When the SPF::MacroString#expand method evaluates the record and finds the d macro, it uses the .authority_domain, which is then concatenated. In the case with nested records, the authority_domain is an instance of SPF::MacroString record, hence the TypeError.

Possible remediation: By calling .to_s to authority_domain here should fix the problem. (It worked as far as I tested).

Thank you for taking a look at this issue. Let me know if there is anything I can help to release the fix.

Hirosvk avatar Jan 15 '25 20:01 Hirosvk

Thank you for reporting this error, I'll look into resolving this as quickly as possible. I will let you know if you can assist further in any way, thank you for pointing me in the right direction!

tedaford avatar Apr 30 '25 03:04 tedaford