Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ PATH
remote: .
specs:
spf (0.1.1)
ruby-ip (~> 0.9.1)

GEM
remote: http://rubygems.org/
Expand Down Expand Up @@ -32,7 +31,6 @@ GEM
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
rspec-support (3.10.3)
ruby-ip (0.9.3)
simplecov (0.21.2)
docile (~> 1.1)
simplecov-html (~> 0.11)
Expand Down
1 change: 0 additions & 1 deletion lib/spf/eval.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# encoding: ASCII-8BIT
require 'ip'
require 'resolv'

require 'spf/error'
Expand Down
12 changes: 6 additions & 6 deletions lib/spf/macro_string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ def expand(context = nil)
elsif char == 'i' # RFC 4408, 8.1/20, 8.1/21
ip_address = request.ip_address
ip_address = SPF::Util.ipv6_address_to_ipv4(ip_address) if SPF::Util.ipv6_address_is_ipv4_mapped(ip_address)
if IP::V4 === ip_address
value = ip_address.to_addr
elsif IP::V6 === ip_address
value = ip_address.to_hex.upcase.split('').join('.')
if ip_address.ipv4?
value = ip_address.to_s
elsif ip_address.ipv6?
value = ip_address.to_i.to_s(16).rjust(32, '0').upcase.split('').join('.')
else
server.throw_result(:permerror, request, "Unexpected IP address version in request")
end
Expand All @@ -92,9 +92,9 @@ def expand(context = nil)
rh_parts = nil
reverse = nil
elsif char == 'v' # RFC 4408, 8.1/6/7
if IP::V4 === request.ip_address
if request.ip_address.ipv4?
value = 'in-addr'
elsif IP::V6 === request.ip_address
elsif request.ip_address.ipv6?
value = 'ip6'
else
# Unexpected IP address version.
Expand Down
55 changes: 24 additions & 31 deletions lib/spf/model.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
# encoding: ASCII-8BIT
require 'ip'
require 'ipaddr'

require 'spf/util'


class IP
def contains?(ip_address)
return (
self.to_irange.first <= ip_address.to_i and
self.to_irange.last >= ip_address.to_i)
end
end


class SPF::Record
DEFAULT_QUALIFIER = '+';
end
Expand Down Expand Up @@ -161,7 +152,7 @@ def parse_ipv4_network(required = false)
self.parse_ipv4_address(required)
self.parse_ipv4_prefix_length
begin
@ip_network = IP.new("#{@ip_address}/#{@ipv4_prefix_length}") if @ip_address and @ipv4_prefix_length
@ip_network = IPAddr.new("#{@ip_address}/#{@ipv4_prefix_length}") if @ip_address and @ipv4_prefix_length
rescue ArgumentError
@ip_network = @ip_address
end
Expand Down Expand Up @@ -200,7 +191,7 @@ def parse_ipv6_network(required = false)
self.parse_ipv6_address(required)
self.parse_ipv6_prefix_length
begin
@ip_network = IP.new("#{@ip_address}/#{@ipv6_prefix_length}") if @ip_address and @ipv6_prefix_length
@ip_network = IPAddr.new("#{@ip_address}/#{@ipv6_prefix_length}") if @ip_address and @ipv6_prefix_length
rescue ArgumentError
@ip_network = @ip_address
end
Expand Down Expand Up @@ -349,13 +340,13 @@ def match_in_domain(server, request, domain)

rrs.each do |rr|
if Resolv::DNS::Resource::IN::A === rr
network = IP.new("#{rr.address}/#{ipv4_prefix_length}")
network = IPAddr.new("#{rr.address}/#{ipv4_prefix_length}")
@ip_netblocks << network
return true if network.contains?(request.ip_address)
return true if network.include?(request.ip_address)
elsif Resolv::DNS::Resource::IN::AAAA === rr
network = IP.new("#{rr.address}/#{ipv6_prefix_length}")
network = IPAddr.new("#{rr.address}/#{ipv6_prefix_length}")
@ip_netblocks << network
return true if network.contains?(request.ip_address_v6)
return true if network.include?(request.ip_address_v6)
else
# Unexpected RR type.
# TODO: Generate debug info or ignore silently.
Expand Down Expand Up @@ -466,11 +457,11 @@ class SPF::Mech::IP4 < SPF::Mech

def parse_params(required = true)
self.parse_ipv4_network(required)
if IP === @ip_network
if IPAddr === @ip_network
@ip_netblocks << @ip_network
if @ip_network.respond_to?(:offset) && @ip_network.offset != 0
if SPF::Util.ip_network_has_host_bits?(@ip_address, @ip_network)
@errors << SPF::InvalidMechCIDRError.new(
"Invalid CIDR netblock - bits in host portion of address of #{@ip_network}"
"Invalid CIDR netblock - bits in host portion of address of #{self.params}"
)
end
end
Expand All @@ -479,19 +470,20 @@ def parse_params(required = true)
def params
return nil unless @ip_network
return @ip_network if String === @ip_network
result = @ip_network.to_addr
if @ip_network.pfxlen != @default_ipv4_prefix_length
result += "/#{@ip_network.pfxlen}"
# Render the mechanism's original, unmasked address.
result = IPAddr.new(@ip_address).to_s
if @ip_network.prefix != @default_ipv4_prefix_length
result += "/#{@ip_network.prefix}"
end
return result
end

def match(server, request, want_result = true)
return false unless @ip_network
ip_network_v6 = IP::V4 === @ip_network ?
ip_network_v6 = (IPAddr === @ip_network and @ip_network.ipv4?) ?
SPF::Util.ipv4_address_to_ipv6(@ip_network) :
@ip_network
return ip_network_v6.contains?(request.ip_address_v6)
return ip_network_v6.include?(request.ip_address_v6)
end

end
Expand All @@ -502,25 +494,26 @@ class SPF::Mech::IP6 < SPF::Mech

def parse_params(required = true)
self.parse_ipv6_network(required)
@ip_netblocks << @ip_network if IP === @ip_network
if @ip_network.respond_to?(:offset) && @ip_network.offset != 0
@ip_netblocks << @ip_network if IPAddr === @ip_network
if SPF::Util.ip_network_has_host_bits?(@ip_address, @ip_network)
@errors << SPF::InvalidMechCIDRError.new(
"Invalid CIDR netblock - bits in host portion of address of #{@ip_network}"
"Invalid CIDR netblock - bits in host portion of address of #{self.params}"
)
end
end

def params
return nil unless @ip_network
return @ip_network if String === @ip_network
params = @ip_network.to_addr
params += '/' + @ip_network.pfxlen.to_s if
@ip_network.pfxlen != self.default_ipv6_prefix_length
# Render the mechanism's original, unmasked address.
params = IPAddr.new(@ip_address).to_s
params += '/' + @ip_network.prefix.to_s if
@ip_network.prefix != self.default_ipv6_prefix_length
return params
end

def match(server, request, want_result = true)
return @ip_network.contains?(request.ip_address_v6)
return @ip_network.include?(request.ip_address_v6)
end

end
Expand Down
12 changes: 6 additions & 6 deletions lib/spf/request.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# encoding: ASCII-8BIT
require 'ip'
require 'ipaddr'

require 'spf/error'

Expand Down Expand Up @@ -101,19 +101,19 @@ def initialize(options = {})

return unless @ip_address

# Ensure ip_address is an IP object:
unless IP === @ip_address
@ip_address = IP.new(@ip_address)
# Ensure ip_address is an IPAddr object:
unless IPAddr === @ip_address
@ip_address = IPAddr.new(@ip_address)
end

# Convert IPv4 address to IPv4-mapped IPv6 address:

if SPF::Util.ipv6_address_is_ipv4_mapped(@ip_address)
@ip_address_v6 = @ip_address # Accept as IPv6 address as-is
@ip_address = SPF::Util.ipv6_address_to_ipv4(@ip_address)
elsif IP::V4 === @ip_address
elsif @ip_address.ipv4?
@ip_address_v6 = SPF::Util.ipv4_address_to_ipv6(@ip_address)
elsif IP::V6 === @ip_address
elsif @ip_address.ipv6?
@ip_address_v6 = @ip_address
else
raise SPF::InvalidOptionValueError.new("Unexpected IP address version");
Expand Down
4 changes: 2 additions & 2 deletions lib/spf/test/scenario.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'spf/test'

require 'ip'
require 'ipaddr'
require 'yaml'

#class Resolv::DNS::Resource::IN::SPF < Resolv::DNS::Resource::IN::TXT
Expand Down Expand Up @@ -56,7 +56,7 @@ def self.new_from_yaml_struct(yaml_struct, options = {})
end
end
elsif type == 'A' or type == 'AAAA'
address = IP.new(data_struct).to_s
address = IPAddr.new(data_struct).to_s
if type == 'A'
record = Resolv::DNS::Resource::IN::A.new(address)
else
Expand Down
42 changes: 26 additions & 16 deletions lib/spf/util.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# encoding: ASCII-8BIT
require 'ip'
require 'ipaddr'
require 'socket'

require 'spf/error'
Expand Down Expand Up @@ -51,15 +51,15 @@ def self.hostname
end

def self.ipv4_address_to_ipv6(ipv4_address)
unless IP::V4 === ipv4_address
raise SPF::InvalidOptionValueError.new('IP::V4 address expected')
unless IPAddr === ipv4_address and ipv4_address.ipv4?
raise SPF::InvalidOptionValueError.new('IPv4 address expected')
end
return IP.new("::ffff:#{ipv4_address.to_addr}/#{ipv4_address.pfxlen - 32 + 128}")
return IPAddr.new("::ffff:#{ipv4_address.to_s}/#{ipv4_address.prefix - 32 + 128}")
end

def self.ipv6_address_to_ipv4(ipv6_address)
unless IP::V6 === ipv6_address and ipv6_address.ipv4_mapped?
raise SPF::InvalidOptionValueError, 'IPv4-mapped IP::V6 address expected'
unless IPAddr === ipv6_address and ipv6_address.ipv6? and ipv6_address.ipv4_mapped?
raise SPF::InvalidOptionValueError, 'IPv4-mapped IPv6 address expected'
end
return ipv6_address.native
end
Expand All @@ -68,25 +68,35 @@ def self.ipv6_address_is_ipv4_mapped(ipv6_address)
return ipv6_address.ipv4_mapped?
end

# Whether the address portion of a CIDR netblock has bits set outside the
# network prefix (e.g. "1.2.3.4/24" has host bits set, "1.2.3.0/24" does not).
# IPAddr masks such bits away on construction, so detection compares the
# original, unmasked address string against the masked network.
def self.ip_network_has_host_bits?(ip_address_string, ip_network)
return false unless IPAddr === ip_network
return IPAddr.new(ip_address_string.to_s).to_i != ip_network.to_i
rescue IPAddr::Error
return false
end

def self.ip_address_to_string(ip_address)
unless IP::V4 === ip_address or IP::V6 === ip_address
raise SPF::InvalidOptionValueError.new('IP::V4 or IP::V6 address expected')
unless IPAddr === ip_address
raise SPF::InvalidOptionValueError.new('IP address expected')
end
return ip_address.to_addr
return ip_address.to_s
end

def self.ip_address_reverse(ip_address)
unless IP::V4 === ip_address or IP::V6 === ip_address
raise SPF::InvalidOptionValueError.new('IP::V4 or IP::V6 address expected')
unless IPAddr === ip_address
raise SPF::InvalidOptionValueError.new('IP address expected')
end
# Treat IPv4-mapped IPv6 addresses as IPv4 addresses:
ip_address = ipv6_address_to_ipv4(ip_address) if ip_address.ipv4_mapped?
case ip_address
when IP::V4
octets = ip_address.to_addr.split('.').first(ip_address.pfxlen / 8)
if ip_address.ipv4?
octets = ip_address.to_s.split('.').first(ip_address.prefix / 8)
return "#{octets .reverse.join('.')}.in-addr.arpa."
when IP::V6
nibbles = ip_address.to_hex .split('') .first(ip_address.pfxlen / 4)
elsif ip_address.ipv6?
nibbles = ip_address.to_i.to_s(16).rjust(32, '0').split('').first(ip_address.prefix / 4)
return "#{nibbles.reverse.join('.')}.ip6.arpa."
end
end
Expand Down
6 changes: 3 additions & 3 deletions spec/macrostring_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
before(:each) do
@request = SPF::Request.new(
identity: 'strong-bad@email.example.com',
ip_address: IP.new('192.0.2.3')
ip_address: IPAddr.new('192.0.2.3')
)
@server = SPF::Server.new
end
Expand Down Expand Up @@ -96,7 +96,7 @@
it 'expands the "v" macro letter to the string "ip6" if <ip> is ipv6' do
request = SPF::Request.new(
identity: 'strong-bad@email.example.com',
ip_address: IP.new('2001:DB8::CB01')
ip_address: IPAddr.new('2001:DB8::CB01')
)
macro_str = described_class.new(
text: '%{v}',
Expand Down Expand Up @@ -215,7 +215,7 @@
it 'expands "%{ir}.%{v}._spf.%{d2}" correctly with an IPv6 request' do
request = SPF::Request.new(
identity: 'strong-bad@email.example.com',
ip_address: IP.new('2001:DB8::CB01')
ip_address: IPAddr.new('2001:DB8::CB01')
)
macro_str = described_class.new(
text: '%{ir}.%{v}._spf.%{d2}',
Expand Down
12 changes: 6 additions & 6 deletions spec/request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
end

it 'has correct ip address' do
expect(request.ip_address.is_a?(IP)).to be_truthy
expect(request.ip_address).to eq IP.new('192.168.0.1')
expect(request.ip_address.is_a?(IPAddr)).to be_truthy
expect(request.ip_address).to eq IPAddr.new('192.168.0.1')
end

it 'has correct helo identity' do
Expand All @@ -49,7 +49,7 @@
it 'creates sub-request object' do
clone = request.new_sub_request(ip_address: '192.168.0.254')
expect(clone.identity).to eq 'fred@example.com'
expect(clone.ip_address).to eq IP.new('192.168.0.254')
expect(clone.ip_address).to eq IPAddr.new('192.168.0.254')
end
end

Expand Down Expand Up @@ -199,8 +199,8 @@

describe 'IP address validation' do

it 'accepts IP object for ip_address' do
ip_address = IP.new('192.168.0.1')
it 'accepts IPAddr object for ip_address' do
ip_address = IPAddr.new('192.168.0.1')
request = SPF::Request.new(
identity: 'fred@example.com',
ip_address: ip_address
Expand All @@ -213,7 +213,7 @@
identity: 'fred@example.com',
ip_address: '::ffff:192.168.0.1'
)
expect(request.ip_address).to eq IP.new('192.168.0.1')
expect(request.ip_address).to eq IPAddr.new('192.168.0.1')
end
end

Expand Down
Loading