coppertone 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/.travis.yml +7 -0
- data/Gemfile +7 -0
- data/LICENSE +201 -0
- data/README.md +58 -0
- data/Rakefile +140 -0
- data/coppertone.gemspec +27 -0
- data/lib/coppertone/class_builder.rb +20 -0
- data/lib/coppertone/directive.rb +38 -0
- data/lib/coppertone/dns/error.rb +9 -0
- data/lib/coppertone/dns/mock_client.rb +106 -0
- data/lib/coppertone/dns/resolv_client.rb +110 -0
- data/lib/coppertone/dns.rb +3 -0
- data/lib/coppertone/domain_spec.rb +45 -0
- data/lib/coppertone/error.rb +29 -0
- data/lib/coppertone/ip_address_wrapper.rb +75 -0
- data/lib/coppertone/macro_context.rb +67 -0
- data/lib/coppertone/macro_string/macro_expand.rb +84 -0
- data/lib/coppertone/macro_string/macro_literal.rb +24 -0
- data/lib/coppertone/macro_string/macro_parser.rb +62 -0
- data/lib/coppertone/macro_string/macro_static_expand.rb +52 -0
- data/lib/coppertone/macro_string.rb +31 -0
- data/lib/coppertone/mechanism/a.rb +16 -0
- data/lib/coppertone/mechanism/all.rb +24 -0
- data/lib/coppertone/mechanism/cidr_parser.rb +14 -0
- data/lib/coppertone/mechanism/domain_spec_mechanism.rb +18 -0
- data/lib/coppertone/mechanism/domain_spec_optional.rb +46 -0
- data/lib/coppertone/mechanism/domain_spec_required.rb +37 -0
- data/lib/coppertone/mechanism/domain_spec_with_dual_cidr.rb +114 -0
- data/lib/coppertone/mechanism/exists.rb +14 -0
- data/lib/coppertone/mechanism/include.rb +18 -0
- data/lib/coppertone/mechanism/include_matcher.rb +34 -0
- data/lib/coppertone/mechanism/ip4.rb +13 -0
- data/lib/coppertone/mechanism/ip6.rb +13 -0
- data/lib/coppertone/mechanism/ip_mechanism.rb +48 -0
- data/lib/coppertone/mechanism/mx.rb +40 -0
- data/lib/coppertone/mechanism/ptr.rb +17 -0
- data/lib/coppertone/mechanism.rb +32 -0
- data/lib/coppertone/modifier/base.rb +24 -0
- data/lib/coppertone/modifier/exp.rb +34 -0
- data/lib/coppertone/modifier/redirect.rb +17 -0
- data/lib/coppertone/modifier/unknown.rb +16 -0
- data/lib/coppertone/modifier.rb +30 -0
- data/lib/coppertone/qualifier.rb +45 -0
- data/lib/coppertone/record.rb +86 -0
- data/lib/coppertone/record_evaluator.rb +63 -0
- data/lib/coppertone/record_finder.rb +34 -0
- data/lib/coppertone/request.rb +68 -0
- data/lib/coppertone/request_context.rb +67 -0
- data/lib/coppertone/request_count_limiter.rb +36 -0
- data/lib/coppertone/result.rb +50 -0
- data/lib/coppertone/sender_identity.rb +39 -0
- data/lib/coppertone/spf_service.rb +9 -0
- data/lib/coppertone/term.rb +13 -0
- data/lib/coppertone/utils/domain_utils.rb +59 -0
- data/lib/coppertone/utils/host_utils.rb +22 -0
- data/lib/coppertone/utils/ip_in_domain_checker.rb +53 -0
- data/lib/coppertone/utils/validated_domain_finder.rb +40 -0
- data/lib/coppertone/utils.rb +4 -0
- data/lib/coppertone/version.rb +3 -0
- data/lib/coppertone.rb +48 -0
- data/lib/resolv/dns/resource/in/spf.rb +15 -0
- data/spec/directive_spec.rb +41 -0
- data/spec/dns/resolv_client_spec.rb +307 -0
- data/spec/domain_spec_spec.rb +35 -0
- data/spec/ip_address_wrapper_spec.rb +67 -0
- data/spec/macro_context_spec.rb +69 -0
- data/spec/macro_string/macro_expand_spec.rb +79 -0
- data/spec/macro_string/macro_literal_spec.rb +27 -0
- data/spec/macro_string/macro_static_expand_spec.rb +67 -0
- data/spec/macro_string_spec.rb +20 -0
- data/spec/mechanism/a_spec.rb +198 -0
- data/spec/mechanism/all_spec.rb +22 -0
- data/spec/mechanism/exists_spec.rb +91 -0
- data/spec/mechanism/include_spec.rb +43 -0
- data/spec/mechanism/ip4_spec.rb +110 -0
- data/spec/mechanism/ip6_spec.rb +104 -0
- data/spec/mechanism/mx_spec.rb +51 -0
- data/spec/mechanism/ptr_spec.rb +43 -0
- data/spec/mechanism_spec.rb +4 -0
- data/spec/modifier_spec.rb +4 -0
- data/spec/open_spf/ALL_mechanism_syntax_spec.rb +38 -0
- data/spec/open_spf/A_mechanism_syntax_spec.rb +159 -0
- data/spec/open_spf/EXISTS_mechanism_syntax_spec.rb +46 -0
- data/spec/open_spf/IP4_mechanism_syntax_spec.rb +59 -0
- data/spec/open_spf/IP6_mechanism_syntax_spec.rb +60 -0
- data/spec/open_spf/Include_mechanism_semantics_and_syntax_spec.rb +56 -0
- data/spec/open_spf/Initial_processing_spec.rb +77 -0
- data/spec/open_spf/MX_mechanism_syntax_spec.rb +119 -0
- data/spec/open_spf/Macro_expansion_rules_spec.rb +154 -0
- data/spec/open_spf/PTR_mechanism_syntax_spec.rb +42 -0
- data/spec/open_spf/Processing_limits_spec.rb +72 -0
- data/spec/open_spf/Record_evaluation_spec.rb +75 -0
- data/spec/open_spf/Record_lookup_spec.rb +48 -0
- data/spec/open_spf/Selecting_records_spec.rb +61 -0
- data/spec/open_spf/Semantics_of_exp_and_other_modifiers_spec.rb +167 -0
- data/spec/open_spf/Test_cases_from_implementation_bugs_spec.rb +17 -0
- data/spec/qualifier_spec.rb +54 -0
- data/spec/record_evaluator_spec.rb +4 -0
- data/spec/record_finder_spec.rb +4 -0
- data/spec/record_spec.rb +100 -0
- data/spec/request_context_spec.rb +43 -0
- data/spec/request_count_limiter_spec.rb +28 -0
- data/spec/result_spec.rb +4 -0
- data/spec/rfc7208-tests.yml +2548 -0
- data/spec/sender_identity_spec.rb +69 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/term_spec.rb +38 -0
- data/spec/utils/domain_utils_spec.rb +60 -0
- data/spec/utils/host_utils_spec.rb +32 -0
- data/spec/utils/ip_in_domain_checker_spec.rb +4 -0
- data/spec/utils/validated_domain_finder_spec.rb +4 -0
- metadata +306 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'MX mechanism syntax' do
|
4
|
+
let(:zonefile) do
|
5
|
+
{ 'mail.example.com' => [{ 'A' => '1.2.3.4' }, { 'MX' => [0, ''] }, { 'TXT' => 'v=spf1 mx' }], 'e1.example.com' => [{ 'TXT' => 'v=spf1 mx/0 -all' }, { 'MX' => [0, 'e1.example.com'] }], 'e2.example.com' => [{ 'A' => '1.1.1.1' }, { 'AAAA' => '1234::2' }, { 'MX' => [0, 'e2.example.com'] }, { 'TXT' => 'v=spf1 mx/0 -all' }], 'e2a.example.com' => [{ 'AAAA' => '1234::1' }, { 'MX' => [0, 'e2a.example.com'] }, { 'TXT' => 'v=spf1 mx//0 -all' }], 'e2b.example.com' => [{ 'A' => '1.1.1.1' }, { 'MX' => [0, 'e2b.example.com'] }, { 'TXT' => 'v=spf1 mx//0 -all' }], 'e3.example.com' => [{ 'TXT' => "v=spf1 mx:foo.example.com\u0000" }], 'e4.example.com' => [{ 'TXT' => 'v=spf1 mx' }, { 'A' => '1.2.3.4' }], 'e5.example.com' => [{ 'TXT' => 'v=spf1 mx:abc.123' }], 'e6.example.com' => [{ 'TXT' => 'v=spf1 mx//33 -all' }], 'e6a.example.com' => [{ 'TXT' => 'v=spf1 mx/33 -all' }], 'e7.example.com' => [{ 'TXT' => 'v=spf1 mx//129 -all' }], 'e9.example.com' => [{ 'TXT' => 'v=spf1 mx:example.com:8080' }], 'e10.example.com' => [{ 'TXT' => 'v=spf1 mx:foo.example.com/24' }], 'foo.example.com' => [{ 'MX' => [0, 'foo1.example.com'] }], 'foo1.example.com' => [{ 'A' => '1.1.1.1' }, { 'A' => '1.2.3.5' }], 'e11.example.com' => [{ 'TXT' => 'v=spf1 mx:foo:bar/baz.example.com' }], 'foo:bar/baz.example.com' => [{ 'MX' => [0, 'foo:bar/baz.example.com'] }, { 'A' => '1.2.3.4' }], 'e12.example.com' => [{ 'TXT' => 'v=spf1 mx:example.-com' }], 'e13.example.com' => [{ 'TXT' => 'v=spf1 mx: -all' }] }
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:dns_client) { Coppertone::DNS::MockClient.new(zonefile) }
|
9
|
+
let(:options) { { dns_client: dns_client } }
|
10
|
+
|
11
|
+
it 'MX = "mx" [ ":" domain-spec ] [ dual-cidr-length ] dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ] ' do
|
12
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e6.example.com', 'mail.example.com', options)
|
13
|
+
expect(%i(fail)).to include(result.code)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'MX = "mx" [ ":" domain-spec ] [ dual-cidr-length ] dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ] ' do
|
17
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e6a.example.com', 'mail.example.com', options)
|
18
|
+
expect(%i(permerror)).to include(result.code)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'MX = "mx" [ ":" domain-spec ] [ dual-cidr-length ] dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ] ' do
|
22
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e7.example.com', 'mail.example.com', options)
|
23
|
+
expect(%i(permerror)).to include(result.code)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'MX matches any returned IP.' do
|
27
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e10.example.com', 'mail.example.com', options)
|
28
|
+
expect(%i(pass)).to include(result.code)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'MX matches any returned IP.' do
|
32
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e10.example.com', 'mail.example.com', options)
|
33
|
+
expect(%i(pass)).to include(result.code)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'domain-spec must pass basic syntax checks' do
|
37
|
+
# A \':\' may appear in domain-spec, but not in top-label.
|
38
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e9.example.com', 'mail.example.com', options)
|
39
|
+
expect(%i(permerror)).to include(result.code)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'If no ips are returned, MX mechanism does not match, even with /0.' do
|
43
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e1.example.com', 'mail.example.com', options)
|
44
|
+
expect(%i(fail)).to include(result.code)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'Matches if any A records for any MX records are present in DNS.' do
|
48
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e2.example.com', 'mail.example.com', options)
|
49
|
+
expect(%i(pass)).to include(result.code)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'cidr4 doesnt apply to IP6 connections.' do
|
53
|
+
# The IP6 CIDR starts with a double slash.
|
54
|
+
result = Coppertone::SpfService.authenticate_email('1234::1', 'foo@e2.example.com', 'mail.example.com', options)
|
55
|
+
expect(%i(fail)).to include(result.code)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'Would match if any AAAA records for MX records are present in DNS, but not for an IP4 connection.' do
|
59
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e2a.example.com', 'mail.example.com', options)
|
60
|
+
expect(%i(fail)).to include(result.code)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'Would match if any AAAA records for MX records are present in DNS, but not for an IP4 connection.' do
|
64
|
+
result = Coppertone::SpfService.authenticate_email('::FFFF:1.2.3.4', 'foo@e2a.example.com', 'mail.example.com', options)
|
65
|
+
expect(%i(fail)).to include(result.code)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'Matches if any AAAA records for any MX records are present in DNS.' do
|
69
|
+
result = Coppertone::SpfService.authenticate_email('1234::1', 'foo@e2a.example.com', 'mail.example.com', options)
|
70
|
+
expect(%i(pass)).to include(result.code)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'No match if no AAAA records for any MX records are present in DNS.' do
|
74
|
+
result = Coppertone::SpfService.authenticate_email('1234::1', 'foo@e2b.example.com', 'mail.example.com', options)
|
75
|
+
expect(%i(fail)).to include(result.code)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'Null not allowed in top-label.' do
|
79
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.5', 'foo@e3.example.com', 'mail.example.com', options)
|
80
|
+
expect(%i(permerror)).to include(result.code)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'Top-label may not be all numeric' do
|
84
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e5.example.com', 'mail.example.com', options)
|
85
|
+
expect(%i(permerror)).to include(result.code)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'Domain-spec may contain any visible char except %' do
|
89
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e11.example.com', 'mail.example.com', options)
|
90
|
+
expect(%i(pass)).to include(result.code)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'Domain-spec may contain any visible char except %' do
|
94
|
+
result = Coppertone::SpfService.authenticate_email('::FFFF:1.2.3.4', 'foo@e11.example.com', 'mail.example.com', options)
|
95
|
+
expect(%i(pass)).to include(result.code)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'Toplabel may not begin with -' do
|
99
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e12.example.com', 'mail.example.com', options)
|
100
|
+
expect(%i(permerror)).to include(result.code)
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'test null MX' do
|
104
|
+
# Some implementations have had trouble with null MX
|
105
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', '', 'mail.example.com', options)
|
106
|
+
expect(%i(neutral)).to include(result.code)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'If the target name has no MX records, check_host() MUST NOT pretend the target is its single MX, and MUST NOT default to an A lookup on the target-name directly.' do
|
110
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e4.example.com', 'mail.example.com', options)
|
111
|
+
expect(%i(neutral)).to include(result.code)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'domain-spec cannot be empty.' do
|
115
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e13.example.com', 'mail.example.com', options)
|
116
|
+
expect(%i(permerror)).to include(result.code)
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Macro expansion rules' do
|
4
|
+
let(:zonefile) do
|
5
|
+
{ 'example.com.d.spf.example.com' => [{ 'TXT' => 'v=spf1 redirect=a.spf.example.com' }], 'a.spf.example.com' => [{ 'TXT' => 'v=spf1 include:o.spf.example.com. ~all' }], 'o.spf.example.com' => [{ 'TXT' => 'v=spf1 ip4:192.168.218.40' }], 'msgbas2x.cos.example.com' => [{ 'A' => '192.168.218.40' }], 'example.com' => [{ 'A' => '192.168.90.76' }, { 'TXT' => 'v=spf1 redirect=%{d}.d.spf.example.com.' }], 'exp.example.com' => [{ 'TXT' => 'v=spf1 exp=msg.example.com. -all' }], 'msg.example.com' => [{ 'TXT' => 'This is a test.' }], 'e1.example.com' => [{ 'TXT' => 'v=spf1 -exists:%(ir).sbl.example.com ?all' }], 'e1e.example.com' => [{ 'TXT' => 'v=spf1 exists:foo%(ir).sbl.example.com ?all' }], 'e1t.example.com' => [{ 'TXT' => 'v=spf1 exists:foo%.sbl.example.com ?all' }], 'e1a.example.com' => [{ 'TXT' => 'v=spf1 a:macro%%percent%_%_space%-url-space.example.com -all' }], 'macro%percent space%20url-space.example.com' => [{ 'A' => '1.2.3.4' }], 'e2.example.com' => [{ 'TXT' => 'v=spf1 -all exp=%{r}.example.com' }], 'e3.example.com' => [{ 'TXT' => 'v=spf1 -all exp=%{ir}.example.com' }], '40.218.168.192.example.com' => [{ 'TXT' => 'Connections from %{c} not authorized.' }], 'somewhat.long.exp.example.com' => [{ 'TXT' => 'v=spf1 -all exp=foobar.%{o}.%{o}.%{o}.%{o}.%{o}.%{o}.%{o}.%{o}.example.com' }], 'somewhat.long.exp.example.com.somewhat.long.exp.example.com.somewhat.long.exp.example.com.somewhat.long.exp.example.com.somewhat.long.exp.example.com.somewhat.long.exp.example.com.somewhat.long.exp.example.com.somewhat.long.exp.example.com.example.com' => [{ 'TXT' => 'Congratulations! That was tricky.' }], 'e4.example.com' => [{ 'TXT' => 'v=spf1 -all exp=e4msg.example.com' }], 'e4msg.example.com' => [{ 'TXT' => '%{c} is queried as %{ir}.%{v}.arpa' }], 'e5.example.com' => [{ 'TXT' => 'v=spf1 a:%{a}.example.com -all' }], 'e6.example.com' => [{ 'TXT' => 'v=spf1 -all exp=e6msg.example.com' }], 'e6msg.example.com' => [{ 'TXT' => 'connect from %{p}' }], 'mx.example.com' => [{ 'A' => '192.168.218.41' }, { 'A' => '192.168.218.42' }, { 'AAAA' => 'CAFE:BABE::2' }, { 'AAAA' => 'CAFE:BABE::3' }], '40.218.168.192.in-addr.arpa' => [{ 'PTR' => 'mx.example.com' }], '41.218.168.192.in-addr.arpa' => [{ 'PTR' => 'mx.example.com' }], '42.218.168.192.in-addr.arpa' => [{ 'PTR' => 'mx.example.com' }, { 'PTR' => 'mx.e7.example.com' }], '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.E.B.A.B.E.F.A.C.ip6.arpa' => [{ 'PTR' => 'mx.example.com' }], '3.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.E.B.A.B.E.F.A.C.ip6.arpa' => [{ 'PTR' => 'mx.example.com' }], 'mx.e7.example.com' => [{ 'A' => '192.168.218.42' }], 'mx.e7.example.com.should.example.com' => [{ 'A' => '127.0.0.2' }], 'mx.example.com.ok.example.com' => [{ 'A' => '127.0.0.2' }], 'e7.example.com' => [{ 'TXT' => 'v=spf1 exists:%{p}.should.example.com ~exists:%{p}.ok.example.com' }], 'e8.example.com' => [{ 'TXT' => 'v=spf1 -all exp=msg8.%{D2}' }], 'msg8.example.com' => [{ 'TXT' => 'http://example.com/why.html?l=%{L}' }], 'e9.example.com' => [{ 'TXT' => 'v=spf1 a:%{H} -all' }], 'e10.example.com' => [{ 'TXT' => 'v=spf1 -include:_spfh.%{d2} ip4:1.2.3.0/24 -all' }], '_spfh.example.com' => [{ 'TXT' => 'v=spf1 -a:%{h} +all' }], 'e11.example.com' => [{ 'TXT' => 'v=spf1 exists:%{i}.%{l2r-}.user.%{d2}' }], '1.2.3.4.gladstone.philip.user.example.com' => [{ 'A' => '127.0.0.2' }], 'e12.example.com' => [{ 'TXT' => 'v=spf1 exists:%{l2r+-}.user.%{d2}' }], 'bar.foo.user.example.com' => [{ 'A' => '127.0.0.2' }] }
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:dns_client) { Coppertone::DNS::MockClient.new(zonefile) }
|
9
|
+
let(:options) { { dns_client: dns_client } }
|
10
|
+
|
11
|
+
it 'trailing dot is ignored for domains' do
|
12
|
+
result = Coppertone::SpfService.authenticate_email('192.168.218.40', 'test@example.com', 'msgbas2x.cos.example.com', options)
|
13
|
+
expect(%i(pass)).to include(result.code)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'trailing dot is not removed from explanation' do
|
17
|
+
# A simple way for an implementation to ignore trailing dots on domains is to remove it when present. But be careful not to remove it for explanation text.
|
18
|
+
result = Coppertone::SpfService.authenticate_email('192.168.218.40', 'test@exp.example.com', 'msgbas2x.cos.example.com', options)
|
19
|
+
expect(%i(fail)).to include(result.code)
|
20
|
+
expect(result.explanation).to eq('This is a test.')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'The following macro letters are allowed only in "exp" text: c, r, t' do
|
24
|
+
result = Coppertone::SpfService.authenticate_email('192.168.218.40', 'test@e2.example.com', 'msgbas2x.cos.example.com', options)
|
25
|
+
expect(%i(permerror)).to include(result.code)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'A % character not followed by a {, %, -, or _ character is a syntax error.' do
|
29
|
+
result = Coppertone::SpfService.authenticate_email('192.168.218.40', 'test@e1.example.com', 'msgbas2x.cos.example.com', options)
|
30
|
+
expect(%i(permerror)).to include(result.code)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'A % character not followed by a {, %, -, or _ character is a syntax error.' do
|
34
|
+
result = Coppertone::SpfService.authenticate_email('192.168.218.40', 'test@e1e.example.com', 'msgbas2x.cos.example.com', options)
|
35
|
+
expect(%i(permerror)).to include(result.code)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'A % character not followed by a {, %, -, or _ character is a syntax error.' do
|
39
|
+
result = Coppertone::SpfService.authenticate_email('192.168.218.40', 'test@e1t.example.com', 'msgbas2x.cos.example.com', options)
|
40
|
+
expect(%i(permerror)).to include(result.code)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'macro-encoded percents (%%), spaces (%_), and URL-percent-encoded spaces (%-)' do
|
44
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'test@e1a.example.com', 'mail.example.com', options)
|
45
|
+
expect(%i(pass)).to include(result.code)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'For IPv4 addresses, both the "i" and "c" macros expand to the standard dotted-quad format.' do
|
49
|
+
result = Coppertone::SpfService.authenticate_email('192.168.218.40', 'test@e3.example.com', 'msgbas2x.cos.example.com', options)
|
50
|
+
expect(%i(fail)).to include(result.code)
|
51
|
+
expect(result.explanation).to eq('Connections from 192.168.218.40 not authorized.')
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'When the result of macro expansion is used in a domain name query, if the expanded domain name exceeds 253 characters, the left side is truncated to fit, by removing successive domain labels until the total length does not exceed 253 characters.' do
|
55
|
+
result = Coppertone::SpfService.authenticate_email('192.168.218.40', 'test@somewhat.long.exp.example.com', 'msgbas2x.cos.example.com', options)
|
56
|
+
expect(%i(fail)).to include(result.code)
|
57
|
+
expect(result.explanation).to eq('Congratulations! That was tricky.')
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'v = the string "in-addr" if <ip> is ipv4, or "ip6" if <ip> is ipv6' do
|
61
|
+
result = Coppertone::SpfService.authenticate_email('192.168.218.40', 'test@e4.example.com', 'msgbas2x.cos.example.com', options)
|
62
|
+
expect(%i(fail)).to include(result.code)
|
63
|
+
expect(result.explanation).to eq('192.168.218.40 is queried as 40.218.168.192.in-addr.arpa')
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'v = the string "in-addr" if <ip> is ipv4, or "ip6" if <ip> is ipv6' do
|
67
|
+
result = Coppertone::SpfService.authenticate_email('CAFE:BABE::1', 'test@e4.example.com', 'msgbas2x.cos.example.com', options)
|
68
|
+
expect(%i(fail)).to include(result.code)
|
69
|
+
expect(result.explanation).to eq('cafe:babe::1 is queried as 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.E.B.A.B.E.F.A.C.ip6.arpa')
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'Allowed macros chars are slodipvh plus crt in explanation.' do
|
73
|
+
result = Coppertone::SpfService.authenticate_email('CAFE:BABE::192.168.218.40', 'test@e5.example.com', 'msgbas2x.cos.example.com', options)
|
74
|
+
expect(%i(permerror)).to include(result.code)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'p = the validated domain name of <ip>' do
|
78
|
+
# The PTR in this example does not validate.
|
79
|
+
result = Coppertone::SpfService.authenticate_email('192.168.218.40', 'test@e6.example.com', 'msgbas2x.cos.example.com', options)
|
80
|
+
expect(%i(fail)).to include(result.code)
|
81
|
+
expect(result.explanation).to eq('connect from unknown')
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'p = the validated domain name of <ip>' do
|
85
|
+
pending 'It is currently unclear, based on RFC 7208, why this spec should pass.'
|
86
|
+
# If a subdomain of the <domain> is present, it SHOULD be used.
|
87
|
+
result = Coppertone::SpfService.authenticate_email('192.168.218.41',
|
88
|
+
'test@e6.example.com',
|
89
|
+
'msgbas2x.cos.example.com', options)
|
90
|
+
expect(%i(fail)).to include(result.code)
|
91
|
+
expect(result.explanation).to eq('connect from mx.example.com')
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'p = the validated domain name of <ip>' do
|
95
|
+
# The PTR in this example does not validate.
|
96
|
+
result = Coppertone::SpfService.authenticate_email('CAFE:BABE::1', 'test@e6.example.com', 'msgbas2x.cos.example.com', options)
|
97
|
+
expect(%i(fail)).to include(result.code)
|
98
|
+
expect(result.explanation).to eq('connect from unknown')
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'p = the validated domain name of <ip>' do
|
102
|
+
pending 'It is currently unclear, based on RFC 7208, why this spec should pass.'
|
103
|
+
# If a subdomain of the <domain> is present, it SHOULD be used.
|
104
|
+
result = Coppertone::SpfService.authenticate_email('CAFE:BABE::3', 'test@e6.example.com', 'msgbas2x.cos.example.com', options)
|
105
|
+
expect(%i(fail)).to include(result.code)
|
106
|
+
expect(result.explanation).to eq('connect from mx.example.com')
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'p = the validated domain name of <ip>' do
|
110
|
+
# If a subdomain of the <domain> is present, it SHOULD be used.
|
111
|
+
result = Coppertone::SpfService.authenticate_email('192.168.218.42', 'test@e7.example.com', 'msgbas2x.cos.example.com', options)
|
112
|
+
expect(%i(pass softfail)).to include(result.code)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'Uppercased macros expand exactly as their lowercased equivalents, and are then URL escaped. All chars not in the unreserved set MUST be escaped.' do
|
116
|
+
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
117
|
+
result = Coppertone::SpfService.authenticate_email('192.168.218.42', '~jack&jill=up-a_b3.c@e8.example.com', 'msgbas2x.cos.example.com', options)
|
118
|
+
expect(%i(fail)).to include(result.code)
|
119
|
+
expect(result.explanation).to eq('http://example.com/why.html?l=~jack%26jill%3Dup-a_b3.c')
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'h = HELO/EHLO domain' do
|
123
|
+
result = Coppertone::SpfService.authenticate_email('192.168.218.40', 'test@e9.example.com', 'msgbas2x.cos.example.com', options)
|
124
|
+
expect(%i(pass)).to include(result.code)
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'h = HELO/EHLO domain, but HELO is invalid' do
|
128
|
+
# Domain-spec must end in either a macro, or a valid toplabel. It is not correct to check syntax after macro expansion.
|
129
|
+
result = Coppertone::SpfService.authenticate_email('192.168.218.40', 'test@e9.example.com', 'JUMPIN\' JUPITER', options)
|
130
|
+
expect(%i(fail)).to include(result.code)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'h = HELO/EHLO domain, but HELO is a domain literal' do
|
134
|
+
# Domain-spec must end in either a macro, or a valid toplabel. It is not correct to check syntax after macro expansion.
|
135
|
+
result = Coppertone::SpfService.authenticate_email('192.168.218.40', 'test@e9.example.com', '[192.168.218.40]', options)
|
136
|
+
expect(%i(fail)).to include(result.code)
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'Example of requiring valid helo in sender policy. This is a complex policy testing several points at once.' do
|
140
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'test@e10.example.com', 'OEMCOMPUTER', options)
|
141
|
+
expect(%i(fail)).to include(result.code)
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'Macro value transformation (splitting on arbitrary characters, reversal, number of right-hand parts to use)' do
|
145
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'philip-gladstone-test@e11.example.com', 'mail.example.com', options)
|
146
|
+
expect(%i(pass)).to include(result.code)
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'Multiple delimiters may be specified in a macro expression. macro-expand = ( "%{" macro-letter transformers *delimiter "}" ) / "%%" / "%_" / "%-"' do
|
150
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo-bar+zip+quux@e12.example.com', 'mail.example.com', options)
|
151
|
+
expect(%i(pass)).to include(result.code)
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'PTR mechanism syntax' do
|
4
|
+
let(:zonefile) do
|
5
|
+
{ 'mail.example.com' => [{ 'A' => '1.2.3.4' }], 'e1.example.com' => [{ 'TXT' => 'v=spf1 ptr/0 -all' }], 'e2.example.com' => [{ 'TXT' => 'v=spf1 ptr:example.com -all' }], '4.3.2.1.in-addr.arpa' => [{ 'PTR' => 'e3.example.com' }, { 'PTR' => 'e4.example.com' }, { 'PTR' => 'mail.example.com' }], '1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.E.B.A.B.E.F.A.C.ip6.arpa' => [{ 'PTR' => 'e3.example.com' }], 'e3.example.com' => [{ 'TXT' => 'v=spf1 ptr -all' }, { 'A' => '1.2.3.4' }, { 'AAAA' => 'CAFE:BABE::1' }], 'e4.example.com' => [{ 'TXT' => 'v=spf1 ptr -all' }], 'e5.example.com' => [{ 'TXT' => 'v=spf1 ptr:' }] }
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:dns_client) { Coppertone::DNS::MockClient.new(zonefile) }
|
9
|
+
let(:options) { { dns_client: dns_client } }
|
10
|
+
|
11
|
+
it 'PTR = "ptr" [ ":" domain-spec ]' do
|
12
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e1.example.com', 'mail.example.com', options)
|
13
|
+
expect(%i(permerror)).to include(result.code)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'Check all validated domain names to see if they end in the <target-name> domain.' do
|
17
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e2.example.com', 'mail.example.com', options)
|
18
|
+
expect(%i(pass)).to include(result.code)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'Check all validated domain names to see if they end in the <target-name> domain.' do
|
22
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e3.example.com', 'mail.example.com', options)
|
23
|
+
expect(%i(pass)).to include(result.code)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'Check all validated domain names to see if they end in the <target-name> domain.' do
|
27
|
+
# This PTR record does not validate
|
28
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e4.example.com', 'mail.example.com', options)
|
29
|
+
expect(%i(fail)).to include(result.code)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'Check all validated domain names to see if they end in the <target-name> domain.' do
|
33
|
+
result = Coppertone::SpfService.authenticate_email('CAFE:BABE::1', 'foo@e3.example.com', 'mail.example.com', options)
|
34
|
+
expect(%i(pass)).to include(result.code)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'domain-spec cannot be empty.' do
|
38
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e5.example.com', 'mail.example.com', options)
|
39
|
+
expect(%i(permerror)).to include(result.code)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Processing limits' do
|
4
|
+
let(:zonefile) do
|
5
|
+
{ 'mail.example.com' => [{ 'A' => '1.2.3.4' }], 'e1.example.com' => [{ 'TXT' => 'v=spf1 ip4:1.1.1.1 redirect=e1.example.com' }, { 'A' => '1.2.3.6' }], 'e2.example.com' => [{ 'TXT' => 'v=spf1 include:e3.example.com' }, { 'A' => '1.2.3.7' }], 'e3.example.com' => [{ 'TXT' => 'v=spf1 include:e2.example.com' }, { 'A' => '1.2.3.8' }], 'e4.example.com' => [{ 'TXT' => 'v=spf1 mx' }, { 'MX' => [0, 'mail.example.com'] }, { 'MX' => [1, 'mail.example.com'] }, { 'MX' => [2, 'mail.example.com'] }, { 'MX' => [3, 'mail.example.com'] }, { 'MX' => [4, 'mail.example.com'] }, { 'MX' => [5, 'mail.example.com'] }, { 'MX' => [6, 'mail.example.com'] }, { 'MX' => [7, 'mail.example.com'] }, { 'MX' => [8, 'mail.example.com'] }, { 'MX' => [9, 'mail.example.com'] }, { 'MX' => [10, 'e4.example.com'] }, { 'A' => '1.2.3.5' }], 'e5.example.com' => [{ 'TXT' => 'v=spf1 ptr' }, { 'A' => '1.2.3.5' }], '5.3.2.1.in-addr.arpa' => [{ 'PTR' => 'e1.example.com.' }, { 'PTR' => 'e2.example.com.' }, { 'PTR' => 'e3.example.com.' }, { 'PTR' => 'e4.example.com.' }, { 'PTR' => 'example.com.' }, { 'PTR' => 'e6.example.com.' }, { 'PTR' => 'e7.example.com.' }, { 'PTR' => 'e8.example.com.' }, { 'PTR' => 'e9.example.com.' }, { 'PTR' => 'e10.example.com.' }, { 'PTR' => 'e5.example.com.' }], 'e6.example.com' => [{ 'TXT' => 'v=spf1 a mx a mx a mx a mx a ptr ip4:1.2.3.4 -all' }, { 'A' => '1.2.3.8' }, { 'MX' => [10, 'e6.example.com'] }], 'e7.example.com' => [{ 'TXT' => 'v=spf1 a mx a mx a mx a mx a ptr a ip4:1.2.3.4 -all' }, { 'A' => '1.2.3.20' }], 'e8.example.com' => [{ 'TXT' => 'v=spf1 a include:inc.example.com ip4:1.2.3.4 mx -all' }, { 'A' => '1.2.3.4' }], 'inc.example.com' => [{ 'TXT' => 'v=spf1 a a a a a a a a' }, { 'A' => '1.2.3.10' }], 'e9.example.com' => [{ 'TXT' => 'v=spf1 a include:inc.example.com a ip4:1.2.3.4 -all' }, { 'A' => '1.2.3.21' }], 'e10.example.com' => [{ 'TXT' => 'v=spf1 a -all' }, { 'A' => '1.2.3.1' }, { 'A' => '1.2.3.2' }, { 'A' => '1.2.3.3' }, { 'A' => '1.2.3.4' }, { 'A' => '1.2.3.5' }, { 'A' => '1.2.3.6' }, { 'A' => '1.2.3.7' }, { 'A' => '1.2.3.8' }, { 'A' => '1.2.3.9' }, { 'A' => '1.2.3.10' }, { 'A' => '1.2.3.11' }, { 'A' => '1.2.3.12' }], 'e11.example.com' => [{ 'TXT' => 'v=spf1 a:err.example.com a:err1.example.com a:err2.example.com ?all' }], 'e12.example.com' => [{ 'TXT' => 'v=spf1 a:err.example.com a:err1.example.com ?all' }] }
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:dns_client) { Coppertone::DNS::MockClient.new(zonefile) }
|
9
|
+
let(:options) { { dns_client: dns_client } }
|
10
|
+
|
11
|
+
it 'SPF implementations MUST limit the number of mechanisms and modifiers that do DNS lookups to at most 10 per SPF check.' do
|
12
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e1.example.com', 'mail.example.com', options)
|
13
|
+
expect(%i(permerror)).to include(result.code)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'SPF implementations MUST limit the number of mechanisms and modifiers that do DNS lookups to at most 10 per SPF check.' do
|
17
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e2.example.com', 'mail.example.com', options)
|
18
|
+
expect(%i(permerror)).to include(result.code)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'there MUST be a limit of no more than 10 MX looked up and checked.' do
|
22
|
+
# The required result for this test was the subject of much controversy with RFC4408. For RFC7208 the ambiguity was resolved in favor of producing a permerror result.
|
23
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.5', 'foo@e4.example.com', 'mail.example.com', options)
|
24
|
+
expect(%i(permerror)).to include(result.code)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'there MUST be a limit of no more than 10 PTR looked up and checked.' do
|
28
|
+
# The result of this test cannot be permerror not only because the RFC does not specify it, but because the sender has no control over the PTR records of spammers. The preferred result reflects evaluating the 10 allowed PTR records in the order returned by the test data. If testing with live DNS, the PTR order may be random, and a pass result would still be compliant. The SPF result is effectively randomized.
|
29
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.5', 'foo@e5.example.com', 'mail.example.com', options)
|
30
|
+
expect(%i(neutral pass)).to include(result.code)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'unlike MX, PTR, there is no RR limit for A' do
|
34
|
+
# There seems to be a tendency for developers to want to limit A RRs in addition to MX and PTR. These are IPs, not usable for 3rd party DoS attacks, and hence need no low limit.
|
35
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.12', 'foo@e10.example.com', 'mail.example.com', options)
|
36
|
+
expect(%i(pass)).to include(result.code)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'SPF implementations MUST limit the number of mechanisms and modifiers that do DNS lookups to at most 10 per SPF check.' do
|
40
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e6.example.com', 'mail.example.com', options)
|
41
|
+
expect(%i(pass)).to include(result.code)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'SPF implementations MUST limit the number of mechanisms and modifiers that do DNS lookups to at most 10 per SPF check.' do
|
45
|
+
# We do not check whether an implementation counts mechanisms before or after evaluation. The RFC is not clear on this.
|
46
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e7.example.com', 'mail.example.com', options)
|
47
|
+
expect(%i(permerror)).to include(result.code)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'SPF implementations MUST limit the number of mechanisms and modifiers that do DNS lookups to at most 10 per SPF check.' do
|
51
|
+
# The part of the RFC that talks about MAY parse the entire record first (4.6) is specific to syntax errors. In RFC7208, processing limits are part of syntax checking (4.6).
|
52
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e8.example.com', 'mail.example.com', options)
|
53
|
+
expect(%i(pass)).to include(result.code)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'SPF implementations MUST limit the number of mechanisms and modifiers that do DNS lookups to at most 10 per SPF check.' do
|
57
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e9.example.com', 'mail.example.com', options)
|
58
|
+
expect(%i(permerror)).to include(result.code)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'SPF implementations SHOULD limit "void lookups" to two. An implementation MAY choose to make such a limit configurable. In this case, a default of two is RECOMMENDED.' do
|
62
|
+
# This is a new check in RFC7208, but it\'s been implemented in Mail::SPF for years with no issues.
|
63
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e12.example.com', 'mail.example.com', options)
|
64
|
+
expect(%i(neutral)).to include(result.code)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'SPF implementations SHOULD limit "void lookups" to two. An implementation MAY choose to make such a limit configurable. In this case, a default of two is RECOMMENDED.' do
|
68
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e11.example.com', 'mail.example.com', options)
|
69
|
+
expect(%i(permerror)).to include(result.code)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Record evaluation' do
|
4
|
+
let(:zonefile) do
|
5
|
+
{ 'mail.example.com' => [{ 'A' => '1.2.3.4' }], 't1.example.com' => [{ 'TXT' => 'v=spf1 ip4:1.2.3.4 -all moo' }], 't2.example.com' => [{ 'TXT' => 'v=spf1 moo.cow-far_out=man:dog/cat ip4:1.2.3.4 -all' }], 't3.example.com' => [{ 'TXT' => 'v=spf1 moo.cow/far_out=man:dog/cat ip4:1.2.3.4 -all' }], 't4.example.com' => [{ 'TXT' => 'v=spf1 moo.cow:far_out=man:dog/cat ip4:1.2.3.4 -all' }], 't5.example.com' => [{ 'TXT' => 'v=spf1 redirect=t5.example.com ~all' }], 't6.example.com' => [{ 'TXT' => 'v=spf1 ip4:1.2.3.4 redirect=t2.example.com' }], 't7.example.com' => [{ 'TXT' => 'v=spf1 ip4:1.2.3.4' }], 't8.example.com' => [{ 'TXT' => 'v=spf1 ip4:1.2.3.4 redirect:t2.example.com' }], 't9.example.com' => [{ 'TXT' => 'v=spf1 a:foo-bar -all' }], 't10.example.com' => [{ 'TXT' => 'v=spf1 a:mail.example...com -all' }], 't11.example.com' => [{ 'TXT' => 'v=spf1 a:a123456789012345678901234567890123456789012345678901234567890123.example.com -all' }], 't12.example.com' => [{ 'TXT' => 'v=spf1 a:%{H}.bar -all' }] }
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:dns_client) { Coppertone::DNS::MockClient.new(zonefile) }
|
9
|
+
let(:options) { { dns_client: dns_client } }
|
10
|
+
|
11
|
+
it 'Any syntax errors anywhere in the record MUST be detected.' do
|
12
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@t1.example.com', 'mail.example.com', options)
|
13
|
+
expect(%i(permerror)).to include(result.code)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'name = ALPHA *( ALPHA / DIGIT / "-" / "_" / "." )' do
|
17
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@t2.example.com', 'mail.example.com', options)
|
18
|
+
expect(%i(pass)).to include(result.code)
|
19
|
+
end
|
20
|
+
|
21
|
+
it '= character immediately after the name and before any ":" or "/"' do
|
22
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@t3.example.com', 'mail.example.com', options)
|
23
|
+
expect(%i(permerror)).to include(result.code)
|
24
|
+
end
|
25
|
+
|
26
|
+
it '= character immediately after the name and before any ":" or "/"' do
|
27
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@t4.example.com', 'mail.example.com', options)
|
28
|
+
expect(%i(permerror)).to include(result.code)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'The "redirect" modifier has an effect after all the mechanisms.' do
|
32
|
+
# The redirect in this example would violate processing limits, except that it is never used because of the all mechanism.
|
33
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@t5.example.com', 'mail.example.com', options)
|
34
|
+
expect(%i(softfail)).to include(result.code)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'The "redirect" modifier has an effect after all the mechanisms.' do
|
38
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.5', 'foo@t6.example.com', 'mail.example.com', options)
|
39
|
+
expect(%i(fail)).to include(result.code)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'Default result is neutral.' do
|
43
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.5', 'foo@t7.example.com', 'mail.example.com', options)
|
44
|
+
expect(%i(neutral)).to include(result.code)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'Invalid mechanism. Redirect is a modifier.' do
|
48
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@t8.example.com', 'mail.example.com', options)
|
49
|
+
expect(%i(permerror)).to include(result.code)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'Domain-spec must end in macro-expand or valid toplabel.' do
|
53
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@t9.example.com', 'mail.example.com', options)
|
54
|
+
expect(%i(permerror)).to include(result.code)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'target-name that is a valid domain-spec per RFC 4408 and RFC 7208 but an invalid domain name per RFC 1035 (empty label) should be treated as non-existent.' do
|
58
|
+
# An empty domain label, i.e. two successive dots, in a mechanism target-name is valid domain-spec syntax (perhaps formed from a macro expansion), even though a DNS query cannot be composed from it. The spec being unclear about it, this could either be considered a syntax error, or, by analogy to 4.3/1 and 5/10/3, the mechanism could be treated as a no-match. RFC 7208 failed to agree on which result to use, and declares the situation undefined. The preferred test result is therefore a matter of opinion.
|
59
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@t10.example.com', 'mail.example.com', options)
|
60
|
+
expect(%i(fail permerror)).to include(result.code)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'target-name that is a valid domain-spec per RFC 4408 and RFC 7208 but an invalid domain name per RFC 1035 (long label) must be treated as non-existent.' do
|
64
|
+
# A domain label longer than 63 characters in a mechanism target-name is valid domain-spec syntax (perhaps formed from a macro expansion), even though a DNS query cannot be composed from it. The spec being unclear about it, this could either be considered a syntax error, or, by analogy to 4.3/1 and 5/10/3, the mechanism could be treated as a no-match. RFC 7208 failed to agree on which result to use, and declares the situation undefined. The preferred test result is therefore a matter of opinion.
|
65
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@t11.example.com', 'mail.example.com', options)
|
66
|
+
expect(%i(fail permerror)).to include(result.code)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'target-name that is a valid domain-spec per RFC 4408 and RFC 7208 but an invalid domain name per RFC 1035 (long label) must be treated as non-existent.' do
|
70
|
+
# A domain label longer than 63 characters that results from macro expansion in a mechanism target-name is valid domain-spec syntax (and is not even subject to syntax checking after macro expansion), even though a DNS query cannot be composed from it. The spec being unclear about it, this could either be considered a syntax error, or, by analogy to 4.3/1 and 5/10/3, the mechanism could be treated as a no-match. RFC 7208 failed to agree on which result to use, and declares the situation undefined. The preferred test result is therefore a matter of opinion.
|
71
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@t12.example.com', '%%%%%%%%%%%%%%%%%%%%%%', options)
|
72
|
+
expect(%i(fail permerror)).to include(result.code)
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Record lookup' do
|
4
|
+
let(:zonefile) do
|
5
|
+
{ 'both.example.net' => [{ 'TXT' => 'v=spf1 -all' }, { 'SPF' => 'v=spf1 -all' }], 'txtonly.example.net' => [{ 'TXT' => 'v=spf1 -all' }], 'spfonly.example.net' => [{ 'SPF' => 'v=spf1 -all' }, { 'TXT' => 'NONE' }], 'spftimeout.example.net' => [{ 'TXT' => 'v=spf1 -all' }, 'TIMEOUT'], 'txttimeout.example.net' => [{ 'SPF' => 'v=spf1 -all' }, { 'TXT' => 'NONE' }, 'TIMEOUT'], 'nospftxttimeout.example.net' => [{ 'SPF' => 'v=spf3 !a:yahoo.com -all' }, { 'TXT' => 'NONE' }, 'TIMEOUT'], 'alltimeout.example.net' => ['TIMEOUT'] }
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:dns_client) { Coppertone::DNS::MockClient.new(zonefile) }
|
9
|
+
let(:options) { { dns_client: dns_client } }
|
10
|
+
|
11
|
+
it 'both' do
|
12
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@both.example.net', 'mail.example.net', options)
|
13
|
+
expect(%i(fail)).to include(result.code)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'Result is none if checking SPF records only (which you should not be doing).' do
|
17
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@txtonly.example.net', 'mail.example.net', options)
|
18
|
+
expect(%i(fail)).to include(result.code)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'Result is none if checking TXT records only.' do
|
22
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@spfonly.example.net', 'mail.example.net', options)
|
23
|
+
expect(%i(none)).to include(result.code)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'TXT record present, but SPF lookup times out. Result is temperror if checking SPF records only. Fortunately, we dont do type SPF anymore.' do
|
27
|
+
# This actually happens for a popular braindead DNS server.
|
28
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@spftimeout.example.net', 'mail.example.net', options)
|
29
|
+
expect(%i(fail)).to include(result.code)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'SPF record present, but TXT lookup times out. If only TXT records are checked, result is temperror.' do
|
33
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@txttimeout.example.net', 'mail.example.net', options)
|
34
|
+
expect(%i(temperror)).to include(result.code)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'No SPF record present, and TXT lookup times out. If only TXT records are checked, result is temperror.' do
|
38
|
+
# Because TXT records is where v=spf1 records will likely be, returning temperror will try again later. A timeout due to a braindead server is unlikely in the case of TXT, as opposed to the newer SPF RR.
|
39
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@nospftxttimeout.example.net', 'mail.example.net', options)
|
40
|
+
expect(%i(temperror)).to include(result.code)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'Both TXT and SPF queries time out' do
|
44
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@alltimeout.example.net', 'mail.example.net', options)
|
45
|
+
expect(%i(temperror)).to include(result.code)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Selecting records' do
|
4
|
+
let(:zonefile) do
|
5
|
+
{ 'example3.com' => [{ 'TXT' => 'v=spf10' }, { 'TXT' => 'v=spf1 mx' }, { 'MX' => [0, 'mail.example1.com'] }], 'example1.com' => [{ 'TXT' => 'v=spf1' }], 'example2.com' => [{ 'TXT' => ['v=spf1', 'mx'] }], 'mail.example1.com' => [{ 'A' => '1.2.3.4' }], 'example4.com' => [{ 'SPF' => 'v=spf1 +all' }, { 'TXT' => 'v=spf1 -all' }], 'example5.com' => [{ 'SPF' => 'v=spf1 +all' }, { 'TXT' => 'v=spf1 -all' }, { 'TXT' => 'v=spf1 +all' }], 'example6.com' => [{ 'TXT' => 'v=spf1 -all' }, { 'TXT' => 'V=sPf1 +all' }], 'example7.com' => [{ 'TXT' => 'v=spf1 -all' }, { 'TXT' => 'v=spf1 -all' }], 'example8.com' => [{ 'SPF' => 'V=spf1 -all' }, { 'SPF' => 'v=spf1 -all' }, { 'TXT' => 'v=spf1 +all' }], 'example9.com' => [{ 'TXT' => 'v=SpF1 ~all' }] }
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:dns_client) { Coppertone::DNS::MockClient.new(zonefile) }
|
9
|
+
let(:options) { { dns_client: dns_client } }
|
10
|
+
|
11
|
+
it 'Version must be terminated by space or end of record. TXT pieces are joined without intervening spaces.' do
|
12
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@example2.com', 'mail.example1.com', options)
|
13
|
+
expect(%i(none)).to include(result.code)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'Empty SPF record.' do
|
17
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@example1.com', 'mail1.example1.com', options)
|
18
|
+
expect(%i(neutral)).to include(result.code)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'nospace2' do
|
22
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@example3.com', 'mail.example1.com', options)
|
23
|
+
expect(%i(pass)).to include(result.code)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'SPF records no longer used.' do
|
27
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@example4.com', 'mail.example1.com', options)
|
28
|
+
expect(%i(fail)).to include(result.code)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'Implementations should give permerror/unknown because of the conflicting TXT records.' do
|
32
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@example5.com', 'mail.example1.com', options)
|
33
|
+
expect(%i(permerror)).to include(result.code)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'Multiple records is a permerror, v=spf1 is case insensitive' do
|
37
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@example6.com', 'mail.example1.com', options)
|
38
|
+
expect(%i(permerror)).to include(result.code)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'Multiple records is a permerror, even when they are identical. However, this situation cannot be reliably reproduced with live DNS since cache and resolvers are allowed to combine identical records.' do
|
42
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@example7.com', 'mail.example1.com', options)
|
43
|
+
expect(%i(permerror fail)).to include(result.code)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'Ignoring SPF-type records will give pass because there is a (single) TXT record.' do
|
47
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@example8.com', 'mail.example1.com', options)
|
48
|
+
expect(%i(pass)).to include(result.code)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'nospf' do
|
52
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@mail.example1.com', 'mail.example1.com', options)
|
53
|
+
expect(%i(none)).to include(result.code)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'v=spf1 is case insensitive' do
|
57
|
+
result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@example9.com', 'mail.example1.com', options)
|
58
|
+
expect(%i(softfail)).to include(result.code)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|