Net::DNS::SPF::Expander
This module expands DNS SPF records, so you don't have to. The problem is that you only get 10 per SPF record, and recursions count against you. Your record won't validate.
Let's say you start with this as an SPF record:
@ TXT "v=spf1 include:_spf.google.com include:sendgrid.net a:hq1.campusexplorer.com a:hq2.campusexplorer.com a:mail2.campusexplorer.com ~all"
You go to http://www.kitterman.com/spf/validate.html and check this record. It passes validation. But later you come back and add salesforce, so that you now have:
@ TXT "v=spf1 include:_spf.google.com include:sendgrid.net include:salesforce.com a:hq1.campusexplorer.com a:hq2.campusexplorer.com a:mail2.campusexplorer.com ~all"
And now your record fails validation.
_spf.google.com takes 3 lookups. _spf1.google.com _spf2.google.com _spf3.google.com sendgrid.net takes 1 lookup. _sendgrid.biz hq1 takes 1 lookup. hq2 takes 1 lookup. mail2 takes 1 lookup.
Salesforce adds:
_spf.google.com (3 you already did) _spf1.google.com _spf2.google.com _spf3.google.com mx takes 4 lookups. salesforce.com.s8a1.psmtp.com. salesforce.com.s8a2.psmtp.com. salesforce.com.s8b1.psmtp.com. salesforce.com.s8b2.psmtp.com.
So now instead of 7 you have 14. The common advice is to expand them, and that is a tedious process. It's especially tedious when, say, salesforce changes their mx record.
So this module and the accompanying script attempt to automate this process for you.
Using the script:
myhost:~/ $ dns-dpf-expander --input_file zone.db myhost:~/ $ ls zone.db zone.db.new zone.db.bak
Using the module:
{ package MyDNSExpander; use Net::DNS::SPF::Expander; my $input_file = '/home/me/project/etc/zone.db'; my $expander = Net::DNS::SPF::Expander->new( input_file => $input_file ); my $string = $expander->write; 1; }
This is the path and name of the zonefile whose SPF records you want to expand. It must be a valid Net::DNS::Zonefile zonefile.
The path and name of the output file. By default, we tack ".new" onto the end of the original filename.
The path and name of the backup file. By default, we tack ".bak" onto the end of the original filename.
A list of nameservers that will be passed to the resolver.
The Net::DNS::Zonefile object created from the input_file.
An arrayref of regexes that we will expand. By default we expand a, mx, include, and redirect records. Configurable.
An arrayref of regexes that we will simply copy over. By default we will copy ip4, ip6, ptr, and exists records. Configurable.
An arrayref of regexes that we will ignore. By default we ignore ?all, exp, v=spf1, and ~all.
We leave out the protocol declaration and the trailing ~all while we are expanding records, so we need to subtract their length from our length calculation.
Default time to live is 10 minutes. Configurable.
The origin of the zonefile. We take it from the zonefile, or you can set it if you like.
An arrayref of all the Net::DNS::RR resource records found in the entire parsed_file.
An arrayref of the Net::DNS::RR::TXT or Net::DNS::RR::SPF records found in the entire parsed_file.
What we use to do the DNS lookups and expand the records. A Net::DNS::Resolver object. You can still set environment variables if you want to change the nameserver it uses.
This is a hashref representing the expanded SPF records. The keys are the names of the SPF records, and the values are hashrefs. Those are keyed on the include, and the values are arrayrefs of the expanded values. There is also a key called "elements" which gathers all the includes into one place, e.g.,
"*.test_zone.com" => { "~all" => undef, elements => [ "ip4:216.239.32.0/19", "ip4:64.233.160.0/19", "ip4:66.249.80.0/20", "ip4:72.14.192.0/18", ... ], "include:_spf.google.com" => [ "ip4:216.239.32.0/19", "ip4:64.233.160.0/19", ... ], "ip4:96.43.144.0/20" => [ "ip4:96.43.144.0/20" ], "v=spf1" => undef }
They are alpha sorted in the final results for predictability in tests.
We need to know how long the expanded record would be, because SPF records should be less than 256 bytes. If the expanded record would be longer than that, we need to split it into pieces.
What sort of records are SPF records? IN records.
Return a Net::DNS::Resolver. Any nameservers will be passed through to the resolver.
Extract the origin from parsed_file.
Tack a ".bak" onto the end of the input_file.
Tack a ".new" onto the end of the input_file.
Turn the Path::Tiny filehandle into a Net::DNS::Zonefile object, so that we can extract the SPF records.
Extract all the resource records from the Net::DNS::Zonefile.
Grep through the _resource_records to find the SPF records. They can be both "TXT" and "SPF" records, so we search for the protocol string, v=spf1.
Calculate the length of each fully expanded SPF record, because they can't be longer than 256 bytes. We have to split them up into multiple records if they are.
This is the only method you really need to call. This expands all your SPF records and writes out the new and the backup files.
Returns a scalar string of the data written to the file.
In case you want to see how your records were expanded, this returns the hashref of Net::DNS::RR objects used to create the new records.
Each component of an SPF record has a prefix, like include:, mx:, etc. Here we chop off the prefix before performing the lookup on the value.
Expand a single SPF record component. This returns either undef or the full SPF record string from Net::DNS::RR::TXT->txtdata.
Recursively call _perform_expansion for each component of the SPF record. This returns an array consisting of the component, e.g., include:salesforce.com, and an arrayref consisting of its full expansion, e.g.,
[ "ip4:216.239.32.0/19", "ip4:64.233.160.0/19", ... "ip6:2c0f:fb50:4000::/36" ]
Create the _expansions hashref from which we generate new SPF records.
Filter ignored elements from component expansions.
The full expansion of a given SPF record is contained in an arrayref, and if the length of the resulting new SPF record would be less than the maximum_record_length, we can use this method to make new Net::DNS::RR objects that will later be stringified for the new SPF record.
The full expansion of a given SPF record is contained in an arrayref, and if the length of the resulting new SPF record would be greater than the maximum_record_length, we have to jump through some hoops to properly split it into new SPF records. Because there will be more than one, and each needs to be less than the maximum_record_length. We do our partitioning here, and then call _new_records_from_arrayref on each of the resulting partitions.
Stringify the Net::DNS::RR::TXT records when they will fit into a single SPF record.
Net::DNS uses fully qualified record names, so that new SPF records will be named *.domain.com, and domain.com, instead of * and @. I prefer the symbols. This code replaces the fully qualified record names with symbols.
Whereas a single new SPF record needs to be concatenated from the stringified Net::DNS::RR::TXTs, and have the trailing ~all added, multiple new SPF records do not need that. They need to be given special _spf names that will then be included in "master" SPF records, and they don't need the trailing ~all.
Create our "master" SPF records that include the split _spf records created in _get_multiple_record_strings, e.g.,
* 600 IN TXT "v=spf1 include:_spf1.test_zone.com include:_spf2.test_zone.com ~all"
Assemble the new DNS zonefile from the lines of the original, comment out the old SPF records, add in the new lines, and append the end of the original.
Amiri Barksdale <amiri@campusexplorer.com>
Neil Bowers <neil@bowers.com>
Marc Bradshaw <marc@marcbradshaw.net>
Karen Etheridge <ether@cpan.org>
Chris Weyl <cweyl@campusexplorer.com>
Copyright (c) 2019 Campus Explorer, Inc.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
Net::DNS
Net::DNS::RR::TXT
MooseX::Getopt
To install Net::DNS::SPF::Expander, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Net::DNS::SPF::Expander
CPAN shell
perl -MCPAN -e shell install Net::DNS::SPF::Expander
For more information on module installation, please visit the detailed CPAN module installation guide.