coppertone 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +34 -0
  3. data/.travis.yml +7 -0
  4. data/Gemfile +7 -0
  5. data/LICENSE +201 -0
  6. data/README.md +58 -0
  7. data/Rakefile +140 -0
  8. data/coppertone.gemspec +27 -0
  9. data/lib/coppertone/class_builder.rb +20 -0
  10. data/lib/coppertone/directive.rb +38 -0
  11. data/lib/coppertone/dns/error.rb +9 -0
  12. data/lib/coppertone/dns/mock_client.rb +106 -0
  13. data/lib/coppertone/dns/resolv_client.rb +110 -0
  14. data/lib/coppertone/dns.rb +3 -0
  15. data/lib/coppertone/domain_spec.rb +45 -0
  16. data/lib/coppertone/error.rb +29 -0
  17. data/lib/coppertone/ip_address_wrapper.rb +75 -0
  18. data/lib/coppertone/macro_context.rb +67 -0
  19. data/lib/coppertone/macro_string/macro_expand.rb +84 -0
  20. data/lib/coppertone/macro_string/macro_literal.rb +24 -0
  21. data/lib/coppertone/macro_string/macro_parser.rb +62 -0
  22. data/lib/coppertone/macro_string/macro_static_expand.rb +52 -0
  23. data/lib/coppertone/macro_string.rb +31 -0
  24. data/lib/coppertone/mechanism/a.rb +16 -0
  25. data/lib/coppertone/mechanism/all.rb +24 -0
  26. data/lib/coppertone/mechanism/cidr_parser.rb +14 -0
  27. data/lib/coppertone/mechanism/domain_spec_mechanism.rb +18 -0
  28. data/lib/coppertone/mechanism/domain_spec_optional.rb +46 -0
  29. data/lib/coppertone/mechanism/domain_spec_required.rb +37 -0
  30. data/lib/coppertone/mechanism/domain_spec_with_dual_cidr.rb +114 -0
  31. data/lib/coppertone/mechanism/exists.rb +14 -0
  32. data/lib/coppertone/mechanism/include.rb +18 -0
  33. data/lib/coppertone/mechanism/include_matcher.rb +34 -0
  34. data/lib/coppertone/mechanism/ip4.rb +13 -0
  35. data/lib/coppertone/mechanism/ip6.rb +13 -0
  36. data/lib/coppertone/mechanism/ip_mechanism.rb +48 -0
  37. data/lib/coppertone/mechanism/mx.rb +40 -0
  38. data/lib/coppertone/mechanism/ptr.rb +17 -0
  39. data/lib/coppertone/mechanism.rb +32 -0
  40. data/lib/coppertone/modifier/base.rb +24 -0
  41. data/lib/coppertone/modifier/exp.rb +34 -0
  42. data/lib/coppertone/modifier/redirect.rb +17 -0
  43. data/lib/coppertone/modifier/unknown.rb +16 -0
  44. data/lib/coppertone/modifier.rb +30 -0
  45. data/lib/coppertone/qualifier.rb +45 -0
  46. data/lib/coppertone/record.rb +86 -0
  47. data/lib/coppertone/record_evaluator.rb +63 -0
  48. data/lib/coppertone/record_finder.rb +34 -0
  49. data/lib/coppertone/request.rb +68 -0
  50. data/lib/coppertone/request_context.rb +67 -0
  51. data/lib/coppertone/request_count_limiter.rb +36 -0
  52. data/lib/coppertone/result.rb +50 -0
  53. data/lib/coppertone/sender_identity.rb +39 -0
  54. data/lib/coppertone/spf_service.rb +9 -0
  55. data/lib/coppertone/term.rb +13 -0
  56. data/lib/coppertone/utils/domain_utils.rb +59 -0
  57. data/lib/coppertone/utils/host_utils.rb +22 -0
  58. data/lib/coppertone/utils/ip_in_domain_checker.rb +53 -0
  59. data/lib/coppertone/utils/validated_domain_finder.rb +40 -0
  60. data/lib/coppertone/utils.rb +4 -0
  61. data/lib/coppertone/version.rb +3 -0
  62. data/lib/coppertone.rb +48 -0
  63. data/lib/resolv/dns/resource/in/spf.rb +15 -0
  64. data/spec/directive_spec.rb +41 -0
  65. data/spec/dns/resolv_client_spec.rb +307 -0
  66. data/spec/domain_spec_spec.rb +35 -0
  67. data/spec/ip_address_wrapper_spec.rb +67 -0
  68. data/spec/macro_context_spec.rb +69 -0
  69. data/spec/macro_string/macro_expand_spec.rb +79 -0
  70. data/spec/macro_string/macro_literal_spec.rb +27 -0
  71. data/spec/macro_string/macro_static_expand_spec.rb +67 -0
  72. data/spec/macro_string_spec.rb +20 -0
  73. data/spec/mechanism/a_spec.rb +198 -0
  74. data/spec/mechanism/all_spec.rb +22 -0
  75. data/spec/mechanism/exists_spec.rb +91 -0
  76. data/spec/mechanism/include_spec.rb +43 -0
  77. data/spec/mechanism/ip4_spec.rb +110 -0
  78. data/spec/mechanism/ip6_spec.rb +104 -0
  79. data/spec/mechanism/mx_spec.rb +51 -0
  80. data/spec/mechanism/ptr_spec.rb +43 -0
  81. data/spec/mechanism_spec.rb +4 -0
  82. data/spec/modifier_spec.rb +4 -0
  83. data/spec/open_spf/ALL_mechanism_syntax_spec.rb +38 -0
  84. data/spec/open_spf/A_mechanism_syntax_spec.rb +159 -0
  85. data/spec/open_spf/EXISTS_mechanism_syntax_spec.rb +46 -0
  86. data/spec/open_spf/IP4_mechanism_syntax_spec.rb +59 -0
  87. data/spec/open_spf/IP6_mechanism_syntax_spec.rb +60 -0
  88. data/spec/open_spf/Include_mechanism_semantics_and_syntax_spec.rb +56 -0
  89. data/spec/open_spf/Initial_processing_spec.rb +77 -0
  90. data/spec/open_spf/MX_mechanism_syntax_spec.rb +119 -0
  91. data/spec/open_spf/Macro_expansion_rules_spec.rb +154 -0
  92. data/spec/open_spf/PTR_mechanism_syntax_spec.rb +42 -0
  93. data/spec/open_spf/Processing_limits_spec.rb +72 -0
  94. data/spec/open_spf/Record_evaluation_spec.rb +75 -0
  95. data/spec/open_spf/Record_lookup_spec.rb +48 -0
  96. data/spec/open_spf/Selecting_records_spec.rb +61 -0
  97. data/spec/open_spf/Semantics_of_exp_and_other_modifiers_spec.rb +167 -0
  98. data/spec/open_spf/Test_cases_from_implementation_bugs_spec.rb +17 -0
  99. data/spec/qualifier_spec.rb +54 -0
  100. data/spec/record_evaluator_spec.rb +4 -0
  101. data/spec/record_finder_spec.rb +4 -0
  102. data/spec/record_spec.rb +100 -0
  103. data/spec/request_context_spec.rb +43 -0
  104. data/spec/request_count_limiter_spec.rb +28 -0
  105. data/spec/result_spec.rb +4 -0
  106. data/spec/rfc7208-tests.yml +2548 -0
  107. data/spec/sender_identity_spec.rb +69 -0
  108. data/spec/spec_helper.rb +8 -0
  109. data/spec/term_spec.rb +38 -0
  110. data/spec/utils/domain_utils_spec.rb +60 -0
  111. data/spec/utils/host_utils_spec.rb +32 -0
  112. data/spec/utils/ip_in_domain_checker_spec.rb +4 -0
  113. data/spec/utils/validated_domain_finder_spec.rb +4 -0
  114. 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