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,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe Coppertone::Mechanism::MX do
4
+ context '#new' do
5
+ it 'should not fail if called with a nil argument' do
6
+ mech = Coppertone::Mechanism::MX.new(nil)
7
+ expect(mech).not_to be_nil
8
+ expect(mech.domain_spec).to be_nil
9
+ expect(mech.ip_v4_cidr_length).to eq(32)
10
+ expect(mech.ip_v6_cidr_length).to eq(128)
11
+ end
12
+
13
+ it 'should not fail if called with a blank argument' do
14
+ mech = Coppertone::Mechanism::MX.new('')
15
+ expect(mech).not_to be_nil
16
+ expect(mech.domain_spec).to be_nil
17
+ expect(mech.ip_v4_cidr_length).to eq(32)
18
+ expect(mech.ip_v6_cidr_length).to eq(128)
19
+ end
20
+
21
+ it 'should fail if called with an invalid macrostring' do
22
+ expect do
23
+ Coppertone::Mechanism::MX.new('abc%:def')
24
+ end.to raise_error(Coppertone::InvalidMechanismError)
25
+ end
26
+ end
27
+
28
+ context '#create' do
29
+ it 'should fail if called with a nil argument' do
30
+ mech = Coppertone::Mechanism::MX.create(nil)
31
+ expect(mech).not_to be_nil
32
+ expect(mech.domain_spec).to be_nil
33
+ expect(mech.ip_v4_cidr_length).to eq(32)
34
+ expect(mech.ip_v6_cidr_length).to eq(128)
35
+ end
36
+
37
+ it 'should fail if called with a blank argument' do
38
+ mech = Coppertone::Mechanism::MX.create('')
39
+ expect(mech).not_to be_nil
40
+ expect(mech.domain_spec).to be_nil
41
+ expect(mech.ip_v4_cidr_length).to eq(32)
42
+ expect(mech.ip_v6_cidr_length).to eq(128)
43
+ end
44
+
45
+ it 'should fail if called with an argument invalid macrostring' do
46
+ expect do
47
+ Coppertone::Mechanism::MX.create('abc%:def')
48
+ end.to raise_error(Coppertone::InvalidMechanismError)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe Coppertone::Mechanism::Ptr do
4
+ context '#new' do
5
+ it 'should not fail if called with a nil argument' do
6
+ mech = Coppertone::Mechanism::Ptr.new(nil)
7
+ expect(mech).not_to be_nil
8
+ expect(mech.domain_spec).to be_nil
9
+ end
10
+
11
+ it 'should not fail if called with a blank argument' do
12
+ mech = Coppertone::Mechanism::Ptr.new('')
13
+ expect(mech).not_to be_nil
14
+ expect(mech.domain_spec).to be_nil
15
+ end
16
+
17
+ it 'should fail if called with an invalid macrostring' do
18
+ expect do
19
+ Coppertone::Mechanism::Ptr.new('abc%:def')
20
+ end.to raise_error(Coppertone::InvalidMechanismError)
21
+ end
22
+ end
23
+
24
+ context '#create' do
25
+ it 'should fail if called with a nil argument' do
26
+ mech = Coppertone::Mechanism::Ptr.create(nil)
27
+ expect(mech).not_to be_nil
28
+ expect(mech.domain_spec).to be_nil
29
+ end
30
+
31
+ it 'should fail if called with a blank argument' do
32
+ mech = Coppertone::Mechanism::Ptr.create('')
33
+ expect(mech).not_to be_nil
34
+ expect(mech.domain_spec).to be_nil
35
+ end
36
+
37
+ it 'should fail if called with an argument invalid macrostring' do
38
+ expect do
39
+ Coppertone::Mechanism::Ptr.create('abc%:def')
40
+ end.to raise_error(Coppertone::InvalidMechanismError)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe Coppertone::Mechanism do
4
+ end
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe Coppertone::Modifier do
4
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'ALL mechanism syntax' do
4
+ let(:zonefile) do
5
+ { 'mail.example.com' => [{ 'A' => '1.2.3.4' }], 'e1.example.com' => [{ 'TXT' => 'v=spf1 -all.' }], 'e2.example.com' => [{ 'TXT' => 'v=spf1 -all:foobar' }], 'e3.example.com' => [{ 'TXT' => 'v=spf1 -all/8' }], 'e4.example.com' => [{ 'TXT' => 'v=spf1 ?all' }], 'e5.example.com' => [{ 'TXT' => 'v=spf1 all -all' }] }
6
+ end
7
+
8
+ let(:dns_client) { Coppertone::DNS::MockClient.new(zonefile) }
9
+ let(:options) { { dns_client: dns_client } }
10
+
11
+ it 'all = "all" ' do
12
+ # At least one implementation got this wrong
13
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e1.example.com', 'mail.example.com', options)
14
+ expect(%i(permerror)).to include(result.code)
15
+ end
16
+
17
+ it 'all = "all" ' do
18
+ # At least one implementation got this wrong
19
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e2.example.com', 'mail.example.com', options)
20
+ expect(%i(permerror)).to include(result.code)
21
+ end
22
+
23
+ it 'all = "all" ' do
24
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e3.example.com', 'mail.example.com', options)
25
+ expect(%i(permerror)).to include(result.code)
26
+ end
27
+
28
+ it 'all = "all" ' do
29
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e4.example.com', 'mail.example.com', options)
30
+ expect(%i(neutral)).to include(result.code)
31
+ end
32
+
33
+ it 'all = "all" ' do
34
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e5.example.com', 'mail.example.com', options)
35
+ expect(%i(pass)).to include(result.code)
36
+ end
37
+
38
+ end
@@ -0,0 +1,159 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'A mechanism syntax' do
4
+ let(:zonefile) do
5
+ { 'mail.example.com' => [{ 'A' => '1.2.3.4' }], 'e1.example.com' => [{ 'TXT' => 'v=spf1 a/0 -all' }], 'e2.example.com' => [{ 'A' => '1.1.1.1' }, { 'AAAA' => '1234::2' }, { 'TXT' => 'v=spf1 a/0 -all' }], 'e2a.example.com' => [{ 'AAAA' => '1234::1' }, { 'TXT' => 'v=spf1 a//0 -all' }], 'e2b.example.com' => [{ 'A' => '1.1.1.1' }, { 'TXT' => 'v=spf1 a//0 -all' }], 'ipv6.example.com' => [{ 'AAAA' => '1234::1' }, { 'A' => '1.1.1.1' }, { 'TXT' => 'v=spf1 a -all' }], 'e3.example.com' => [{ 'TXT' => "v=spf1 a:foo.example.com\u0000" }], 'e4.example.com' => [{ 'TXT' => 'v=spf1 a:111.222.33.44' }], 'e5.example.com' => [{ 'TXT' => 'v=spf1 a:abc.123' }], 'e5a.example.com' => [{ 'TXT' => 'v=spf1 a:museum' }], 'e5b.example.com' => [{ 'TXT' => 'v=spf1 a:museum.' }], 'e6.example.com' => [{ 'TXT' => 'v=spf1 a//33 -all' }], 'e6a.example.com' => [{ 'TXT' => 'v=spf1 a/33 -all' }], 'e7.example.com' => [{ 'TXT' => 'v=spf1 a//129 -all' }], 'e8.example.com' => [{ 'A' => '1.2.3.5' }, { 'AAAA' => '2001:db8:1234::dead:beef' }, { 'TXT' => 'v=spf1 a/24//64 -all' }], 'e8e.example.com' => [{ 'A' => '1.2.3.5' }, { 'AAAA' => '2001:db8:1234::dead:beef' }, { 'TXT' => 'v=spf1 a/24/64 -all' }], 'e8a.example.com' => [{ 'A' => '1.2.3.5' }, { 'AAAA' => '2001:db8:1234::dead:beef' }, { 'TXT' => 'v=spf1 a/24 -all' }], 'e8b.example.com' => [{ 'A' => '1.2.3.5' }, { 'AAAA' => '2001:db8:1234::dead:beef' }, { 'TXT' => 'v=spf1 a//64 -all' }], 'e9.example.com' => [{ 'TXT' => 'v=spf1 a:example.com:8080' }], 'e10.example.com' => [{ 'TXT' => 'v=spf1 a:foo.example.com/24' }], 'foo.example.com' => [{ 'A' => '1.1.1.1' }, { 'A' => '1.2.3.5' }], 'e11.example.com' => [{ 'TXT' => 'v=spf1 a:foo:bar/baz.example.com' }], 'foo:bar/baz.example.com' => [{ 'A' => '1.2.3.4' }], 'e12.example.com' => [{ 'TXT' => 'v=spf1 a:example.-com' }], 'e13.example.com' => [{ 'TXT' => 'v=spf1 a:' }], 'e14.example.com' => [{ 'TXT' => 'v=spf1 a:foo.example.xn--zckzah -all' }], 'foo.example.xn--zckzah' => [{ 'A' => '1.2.3.4' }] }
6
+ end
7
+
8
+ let(:dns_client) { Coppertone::DNS::MockClient.new(zonefile) }
9
+ let(:options) { { dns_client: dns_client } }
10
+
11
+ it 'A = "a" [ ":" 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 'A = "a" [ ":" 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 'A = "a" [ ":" 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 'A = "a" [ ":" domain-spec ] [ dual-cidr-length ] dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ] ' do
27
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e8.example.com', 'mail.example.com', options)
28
+ expect(%i(pass)).to include(result.code)
29
+ end
30
+
31
+ it 'A = "a" [ ":" domain-spec ] [ dual-cidr-length ] dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ] ' do
32
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e8e.example.com', 'mail.example.com', options)
33
+ expect(%i(permerror)).to include(result.code)
34
+ end
35
+
36
+ it 'A = "a" [ ":" domain-spec ] [ dual-cidr-length ] dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ] ' do
37
+ result = Coppertone::SpfService.authenticate_email('2001:db8:1234::cafe:babe', 'foo@e8.example.com', 'mail.example.com', options)
38
+ expect(%i(pass)).to include(result.code)
39
+ end
40
+
41
+ it 'A = "a" [ ":" domain-spec ] [ dual-cidr-length ] dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ] ' do
42
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e8b.example.com', 'mail.example.com', options)
43
+ expect(%i(fail)).to include(result.code)
44
+ end
45
+
46
+ it 'A = "a" [ ":" domain-spec ] [ dual-cidr-length ] dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ] ' do
47
+ result = Coppertone::SpfService.authenticate_email('2001:db8:1234::cafe:babe', 'foo@e8a.example.com', 'mail.example.com', options)
48
+ expect(%i(fail)).to include(result.code)
49
+ end
50
+
51
+ it 'A matches any returned IP.' do
52
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e10.example.com', 'mail.example.com', options)
53
+ expect(%i(pass)).to include(result.code)
54
+ end
55
+
56
+ it 'A matches any returned IP.' do
57
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e10.example.com', 'mail.example.com', options)
58
+ expect(%i(pass)).to include(result.code)
59
+ end
60
+
61
+ it 'domain-spec must pass basic syntax checks; a : may appear in domain-spec, but not in top-label' do
62
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e9.example.com', 'mail.example.com', options)
63
+ expect(%i(permerror)).to include(result.code)
64
+ end
65
+
66
+ it 'If no ips are returned, A mechanism does not match, even with /0.' do
67
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e1.example.com', 'mail.example.com', options)
68
+ expect(%i(fail)).to include(result.code)
69
+ end
70
+
71
+ it 'Matches if any A records are present in DNS.' do
72
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e2.example.com', 'mail.example.com', options)
73
+ expect(%i(pass)).to include(result.code)
74
+ end
75
+
76
+ it 'Matches if any A records are present in DNS.' do
77
+ result = Coppertone::SpfService.authenticate_email('1234::1', 'foo@e2.example.com', 'mail.example.com', options)
78
+ expect(%i(fail)).to include(result.code)
79
+ end
80
+
81
+ it 'Would match if any AAAA records are present in DNS, but not for an IP4 connection.' do
82
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e2a.example.com', 'mail.example.com', options)
83
+ expect(%i(fail)).to include(result.code)
84
+ end
85
+
86
+ it 'Would match if any AAAA records are present in DNS, but not for an IP4 connection.' do
87
+ result = Coppertone::SpfService.authenticate_email('::FFFF:1.2.3.4', 'foo@e2a.example.com', 'mail.example.com', options)
88
+ expect(%i(fail)).to include(result.code)
89
+ end
90
+
91
+ it 'Matches if any AAAA records are present in DNS.' do
92
+ result = Coppertone::SpfService.authenticate_email('1234::1', 'foo@e2a.example.com', 'mail.example.com', options)
93
+ expect(%i(pass)).to include(result.code)
94
+ end
95
+
96
+ it 'Simple IP6 Address match with dual stack.' do
97
+ result = Coppertone::SpfService.authenticate_email('1234::1', 'foo@ipv6.example.com', 'mail.example.com', options)
98
+ expect(%i(pass)).to include(result.code)
99
+ end
100
+
101
+ it 'No match if no AAAA records are present in DNS.' do
102
+ result = Coppertone::SpfService.authenticate_email('1234::1', 'foo@e2b.example.com', 'mail.example.com', options)
103
+ expect(%i(fail)).to include(result.code)
104
+ end
105
+
106
+ it 'Null octets not allowed in toplabel' do
107
+ result = Coppertone::SpfService.authenticate_email('1.2.3.5', 'foo@e3.example.com', 'mail.example.com', options)
108
+ expect(%i(permerror)).to include(result.code)
109
+ end
110
+
111
+ it 'toplabel may not be all numeric' do
112
+ # A common publishing mistake is using ip4 addresses with A mechanism. This should receive special diagnostic attention in the permerror.
113
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e4.example.com', 'mail.example.com', options)
114
+ expect(%i(permerror)).to include(result.code)
115
+ end
116
+
117
+ it 'toplabel may not be all numeric' do
118
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e5.example.com', 'mail.example.com', options)
119
+ expect(%i(permerror)).to include(result.code)
120
+ end
121
+
122
+ it 'toplabel may contain dashes' do
123
+ # Going from the "toplabel" grammar definition, an implementation using regular expressions in incrementally parsing SPF records might erroneously try to match a TLD such as ".xn--zckzah" (cf. IDN TLDs!) to \'( *alphanum ALPHA *alphanum )\' first before trying the alternative \'( 1*alphanum "-" *( alphanum / "-" ) alphanum )\', essentially causing a non-greedy, and thus, incomplete match. Make sure a greedy match is performed!
124
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e14.example.com', 'mail.example.com', options)
125
+ expect(%i(pass)).to include(result.code)
126
+ end
127
+
128
+ it 'toplabel may not begin with a dash' do
129
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e12.example.com', 'mail.example.com', options)
130
+ expect(%i(permerror)).to include(result.code)
131
+ end
132
+
133
+ it 'domain-spec may not consist of only a toplabel.' do
134
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e5a.example.com', 'mail.example.com', options)
135
+ expect(%i(permerror)).to include(result.code)
136
+ end
137
+
138
+ it 'domain-spec may not consist of only a toplabel.' do
139
+ # "A trailing dot doesn\'t help."
140
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e5b.example.com', 'mail.example.com', options)
141
+ expect(%i(permerror)).to include(result.code)
142
+ end
143
+
144
+ it 'domain-spec may contain any visible char except %' do
145
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e11.example.com', 'mail.example.com', options)
146
+ expect(%i(pass)).to include(result.code)
147
+ end
148
+
149
+ it 'domain-spec may contain any visible char except %' do
150
+ result = Coppertone::SpfService.authenticate_email('::FFFF:1.2.3.4', 'foo@e11.example.com', 'mail.example.com', options)
151
+ expect(%i(pass)).to include(result.code)
152
+ end
153
+
154
+ it 'domain-spec cannot be empty.' do
155
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e13.example.com', 'mail.example.com', options)
156
+ expect(%i(permerror)).to include(result.code)
157
+ end
158
+
159
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'EXISTS mechanism syntax' do
4
+ let(:zonefile) do
5
+ { 'mail.example.com' => [{ 'A' => '1.2.3.4' }], 'mail6.example.com' => [{ 'AAAA' => 'CAFE:BABE::4' }], 'err.example.com' => ['TIMEOUT'], 'e1.example.com' => [{ 'TXT' => 'v=spf1 exists:' }], 'e2.example.com' => [{ 'TXT' => 'v=spf1 exists' }], 'e3.example.com' => [{ 'TXT' => 'v=spf1 exists:mail.example.com/24' }], 'e4.example.com' => [{ 'TXT' => 'v=spf1 exists:mail.example.com' }], 'e5.example.com' => [{ 'TXT' => 'v=spf1 exists:mail6.example.com -all' }], 'e6.example.com' => [{ 'TXT' => 'v=spf1 exists:err.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 'domain-spec cannot be empty.' 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 'exists = "exists" ":" domain-spec' 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 'exists = "exists" ":" domain-spec' do
22
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e3.example.com', 'mail.example.com', options)
23
+ expect(%i(permerror)).to include(result.code)
24
+ end
25
+
26
+ it 'mechanism matches if any DNS A RR exists' do
27
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e4.example.com', 'mail.example.com', options)
28
+ expect(%i(pass)).to include(result.code)
29
+ end
30
+
31
+ it 'The lookup type is A even when the connection is ip6' do
32
+ result = Coppertone::SpfService.authenticate_email('CAFE:BABE::3', 'foo@e4.example.com', 'mail.example.com', options)
33
+ expect(%i(pass)).to include(result.code)
34
+ end
35
+
36
+ it 'The lookup type is A even when the connection is ip6' do
37
+ result = Coppertone::SpfService.authenticate_email('CAFE:BABE::3', 'foo@e5.example.com', 'mail.example.com', options)
38
+ expect(%i(fail)).to include(result.code)
39
+ end
40
+
41
+ it 'Result for DNS error clarified in RFC7208: MTAs or other processors SHOULD impose a limit on the maximum amount of elapsed time to evaluate check_host(). Such a limit SHOULD allow at least 20 seconds. If such a limit is exceeded, the result of authorization SHOULD be "temperror".' do
42
+ result = Coppertone::SpfService.authenticate_email('CAFE:BABE::3', 'foo@e6.example.com', 'mail.example.com', options)
43
+ expect(%i(temperror)).to include(result.code)
44
+ end
45
+
46
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'IP4 mechanism syntax' 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/0 -all' }], 'e2.example.com' => [{ 'TXT' => 'v=spf1 ip4:1.2.3.4/32 -all' }], 'e3.example.com' => [{ 'TXT' => 'v=spf1 ip4:1.2.3.4/33 -all' }], 'e4.example.com' => [{ 'TXT' => 'v=spf1 ip4:1.2.3.4/032 -all' }], 'e5.example.com' => [{ 'TXT' => 'v=spf1 ip4' }], 'e6.example.com' => [{ 'TXT' => 'v=spf1 ip4:1.2.3.4//32' }], 'e7.example.com' => [{ 'TXT' => 'v=spf1 -ip4:1.2.3.4 ip6:::FFFF:1.2.3.4' }], 'e8.example.com' => [{ 'TXT' => 'v=spf1 ip4:1.2.3.4:8080' }], 'e9.example.com' => [{ 'TXT' => 'v=spf1 ip4:1.2.3' }] }
6
+ end
7
+
8
+ let(:dns_client) { Coppertone::DNS::MockClient.new(zonefile) }
9
+ let(:options) { { dns_client: dns_client } }
10
+
11
+ it 'ip4-cidr-length = "/" 1*DIGIT' do
12
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e1.example.com', 'mail.example.com', options)
13
+ expect(%i(pass)).to include(result.code)
14
+ end
15
+
16
+ it 'ip4-cidr-length = "/" 1*DIGIT' 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 'Invalid CIDR should get permerror.' do
22
+ # The RFC4408 was silent on ip4 CIDR > 32 or ip6 CIDR > 128, but RFC7208 is explicit. Invalid CIDR is prohibited.
23
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e3.example.com', 'mail.example.com', options)
24
+ expect(%i(permerror)).to include(result.code)
25
+ end
26
+
27
+ it 'Invalid CIDR should get permerror.' do
28
+ # Leading zeros are not explicitly prohibited by the RFC. However, since the RFC explicity prohibits leading zeros in ip4-network, our interpretation is that CIDR should be also.
29
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e4.example.com', 'mail.example.com', options)
30
+ expect(%i(permerror)).to include(result.code)
31
+ end
32
+
33
+ it 'IP4 = "ip4" ":" ip4-network [ ip4-cidr-length ]' do
34
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e5.example.com', 'mail.example.com', options)
35
+ expect(%i(permerror)).to include(result.code)
36
+ end
37
+
38
+ it 'IP4 = "ip4" ":" ip4-network [ ip4-cidr-length ]' do
39
+ # This has actually been published in SPF records.
40
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e8.example.com', 'mail.example.com', options)
41
+ expect(%i(permerror)).to include(result.code)
42
+ end
43
+
44
+ it 'It is not permitted to omit parts of the IP address instead of using CIDR notations.' do
45
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e9.example.com', 'mail.example.com', options)
46
+ expect(%i(permerror)).to include(result.code)
47
+ end
48
+
49
+ it 'dual-cidr-length not permitted on ip4' do
50
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e6.example.com', 'mail.example.com', options)
51
+ expect(%i(permerror)).to include(result.code)
52
+ end
53
+
54
+ it 'IP4 mapped IP6 connections MUST be treated as IP4' do
55
+ result = Coppertone::SpfService.authenticate_email('::FFFF:1.2.3.4', 'foo@e7.example.com', 'mail.example.com', options)
56
+ expect(%i(fail)).to include(result.code)
57
+ end
58
+
59
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'IP6 mechanism syntax' do
4
+ let(:zonefile) do
5
+ { 'mail.example.com' => [{ 'A' => '1.2.3.4' }], 'e1.example.com' => [{ 'TXT' => 'v=spf1 -all ip6' }], 'e2.example.com' => [{ 'TXT' => 'v=spf1 ip6:::1.1.1.1/0' }], 'e3.example.com' => [{ 'TXT' => 'v=spf1 ip6:::1.1.1.1/129' }], 'e4.example.com' => [{ 'TXT' => 'v=spf1 ip6:::1.1.1.1//33' }], 'e5.example.com' => [{ 'TXT' => 'v=spf1 ip6:CAFE:BABE:8000::/33' }], 'e6.example.com' => [{ 'TXT' => 'v=spf1 ip6::CAFE::BABE' }] }
6
+ end
7
+
8
+ let(:dns_client) { Coppertone::DNS::MockClient.new(zonefile) }
9
+ let(:options) { { dns_client: dns_client } }
10
+
11
+ it 'IP6 = "ip6" ":" ip6-network [ ip6-cidr-length ]' 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 'IP4 connections do not match ip6.' do
17
+ # There was controversy over IPv4 mapped connections. RFC7208 clearly states IPv4 mapped addresses only match ip4: mechanisms.
18
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e2.example.com', 'mail.example.com', options)
19
+ expect(%i(neutral)).to include(result.code)
20
+ end
21
+
22
+ it 'Even if the SMTP connection is via IPv6, an IPv4-mapped IPv6 IP address (see RFC 3513, Section 2.5.5) MUST still be considered an IPv4 address.' do
23
+ # There was controversy over ip4 mapped connections. RFC7208 clearly requires such connections to be considered as ip4 only.
24
+ result = Coppertone::SpfService.authenticate_email('::FFFF:1.2.3.4', 'foo@e2.example.com', 'mail.example.com', options)
25
+ expect(%i(neutral)).to include(result.code)
26
+ end
27
+
28
+ it 'Match any IP6' do
29
+ result = Coppertone::SpfService.authenticate_email('DEAF:BABE::CAB:FEE', 'foo@e2.example.com', 'mail.example.com', options)
30
+ expect(%i(pass)).to include(result.code)
31
+ end
32
+
33
+ it 'Invalid CIDR' do
34
+ # IP4 only implementations MUST fully syntax check all mechanisms, even if they otherwise ignore them.
35
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e3.example.com', 'mail.example.com', options)
36
+ expect(%i(permerror)).to include(result.code)
37
+ end
38
+
39
+ it 'dual-cidr syntax not used for ip6' do
40
+ # IP4 only implementations MUST fully syntax check all mechanisms, even if they otherwise ignore them.
41
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e4.example.com', 'mail.example.com', options)
42
+ expect(%i(permerror)).to include(result.code)
43
+ end
44
+
45
+ it 'make sure ip4 cidr restriction are not used for ip6' do
46
+ result = Coppertone::SpfService.authenticate_email('CAFE:BABE:8000::', 'foo@e5.example.com', 'mail.example.com', options)
47
+ expect(%i(pass)).to include(result.code)
48
+ end
49
+
50
+ it 'make sure ip4 cidr restriction are not used for ip6' do
51
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e5.example.com', 'mail.example.com', options)
52
+ expect(%i(neutral)).to include(result.code)
53
+ end
54
+
55
+ it '' do
56
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e6.example.com', 'mail.example.com', options)
57
+ expect(%i(permerror)).to include(result.code)
58
+ end
59
+
60
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Include mechanism semantics and syntax' do
4
+ let(:zonefile) do
5
+ { 'mail.example.com' => [{ 'A' => '1.2.3.4' }], 'ip5.example.com' => [{ 'TXT' => 'v=spf1 ip4:1.2.3.5 -all' }], 'ip6.example.com' => [{ 'TXT' => 'v=spf1 ip4:1.2.3.6 ~all' }], 'ip7.example.com' => [{ 'TXT' => 'v=spf1 ip4:1.2.3.7 ?all' }], 'ip8.example.com' => ['TIMEOUT'], 'erehwon.example.com' => [{ 'TXT' => 'v=spfl am not an SPF record' }], 'e1.example.com' => [{ 'TXT' => 'v=spf1 include:ip5.example.com ~all' }], 'e2.example.com' => [{ 'TXT' => 'v=spf1 include:ip6.example.com all' }], 'e3.example.com' => [{ 'TXT' => 'v=spf1 include:ip7.example.com -all' }], 'e4.example.com' => [{ 'TXT' => 'v=spf1 include:ip8.example.com -all' }], 'e5.example.com' => [{ 'TXT' => 'v=spf1 include:e6.example.com -all' }], 'e6.example.com' => [{ 'TXT' => 'v=spf1 include +all' }], 'e7.example.com' => [{ 'TXT' => 'v=spf1 include:erehwon.example.com -all' }], 'e8.example.com' => [{ 'TXT' => 'v=spf1 include: -all' }], 'e9.example.com' => [{ 'TXT' => 'v=spf1 include:ip5.example.com/24 -all' }] }
6
+ end
7
+
8
+ let(:dns_client) { Coppertone::DNS::MockClient.new(zonefile) }
9
+ let(:options) { { dns_client: dns_client } }
10
+
11
+ it 'recursive check_host() result of fail causes include to not match.' do
12
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e1.example.com', 'mail.example.com', options)
13
+ expect(%i(softfail)).to include(result.code)
14
+ end
15
+
16
+ it 'recursive check_host() result of softfail causes include to not match.' 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 'recursive check_host() result of neutral causes include to not match.' do
22
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e3.example.com', 'mail.example.com', options)
23
+ expect(%i(fail)).to include(result.code)
24
+ end
25
+
26
+ it 'recursive check_host() result of temperror causes include to temperror' do
27
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e4.example.com', 'mail.example.com', options)
28
+ expect(%i(temperror)).to include(result.code)
29
+ end
30
+
31
+ it 'recursive check_host() result of permerror causes include to permerror' do
32
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e5.example.com', 'mail.example.com', options)
33
+ expect(%i(permerror)).to include(result.code)
34
+ end
35
+
36
+ it 'include = "include" ":" domain-spec' do
37
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e6.example.com', 'mail.example.com', options)
38
+ expect(%i(permerror)).to include(result.code)
39
+ end
40
+
41
+ it 'include = "include" ":" domain-spec' do
42
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e9.example.com', 'mail.example.com', options)
43
+ expect(%i(permerror)).to include(result.code)
44
+ end
45
+
46
+ it 'recursive check_host() result of none causes include to permerror' do
47
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e7.example.com', 'mail.example.com', options)
48
+ expect(%i(permerror)).to include(result.code)
49
+ end
50
+
51
+ it 'domain-spec cannot be empty.' do
52
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foo@e8.example.com', 'mail.example.com', options)
53
+ expect(%i(permerror)).to include(result.code)
54
+ end
55
+
56
+ end
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Initial processing' do
4
+ let(:zonefile) do
5
+ { 'example.com' => ['TIMEOUT'], 'example.net' => [{ 'TXT' => 'v=spf1 -all exp=exp.example.net' }], 'a.example.net' => [{ 'TXT' => 'v=spf1 -all exp=exp.example.net' }], 'exp.example.net' => [{ 'TXT' => '%{l}' }], 'a12345678901234567890123456789012345678901234567890123456789012.example.com' => [{ 'TXT' => 'v=spf1 -all' }], 'hosed.example.com' => [{ 'TXT' => 'v=spf1 a:garbage.example.net -all' }], 'hosed2.example.com' => [{ 'TXT' => "v=spf1 \u0080a:example.net -all" }], 'hosed3.example.com' => [{ 'TXT' => "v=spf1 a:example.net \u0096all" }], 'nothosed.example.com' => [{ 'TXT' => 'v=spf1 a:example.net -all' }, { 'TXT' => "\u0096" }], 'ctrl.example.com' => [{ 'TXT' => "v=spf1 a:ctrl.example.com\rptr -all" }, { 'A' => '192.0.2.3' }] }
6
+ end
7
+
8
+ let(:dns_client) { Coppertone::DNS::MockClient.new(zonefile) }
9
+ let(:options) { { dns_client: dns_client } }
10
+
11
+ it 'DNS labels limited to 63 chars.' do
12
+ # For initial processing, a long label results in None, not TempError
13
+ result = Coppertone::SpfService.authenticate_email('1.2.3.5', 'lyme.eater@A123456789012345678901234567890123456789012345678901234567890123.example.com', 'mail.example.net', options)
14
+ expect(%i(none)).to include(result.code)
15
+ end
16
+
17
+ it 'DNS labels limited to 63 chars.' do
18
+ result = Coppertone::SpfService.authenticate_email('1.2.3.5', 'lyme.eater@A12345678901234567890123456789012345678901234567890123456789012.example.com', 'mail.example.net', options)
19
+ expect(%i(fail)).to include(result.code)
20
+ end
21
+
22
+ it 'emptylabel' do
23
+ result = Coppertone::SpfService.authenticate_email('1.2.3.5', 'lyme.eater@A...example.com', 'mail.example.net', options)
24
+ expect(%i(none)).to include(result.code)
25
+ end
26
+
27
+ it 'helo-not-fqdn' do
28
+ result = Coppertone::SpfService.authenticate_email('1.2.3.5', '', 'A2345678', options)
29
+ expect(%i(none)).to include(result.code)
30
+ end
31
+
32
+ it 'helo-domain-literal' do
33
+ result = Coppertone::SpfService.authenticate_email('1.2.3.5', '', '[1.2.3.5]', options)
34
+ expect(%i(none)).to include(result.code)
35
+ end
36
+
37
+ it 'nolocalpart' do
38
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', '@example.net', 'mail.example.net', options)
39
+ expect(%i(fail)).to include(result.code)
40
+ expect(result.explanation).to eq('postmaster')
41
+ end
42
+
43
+ it 'domain-literal' do
44
+ result = Coppertone::SpfService.authenticate_email('1.2.3.5', 'foo@[1.2.3.5]', 'OEMCOMPUTER', options)
45
+ expect(%i(none)).to include(result.code)
46
+ end
47
+
48
+ it 'SPF policies are restricted to 7-bit ascii.' do
49
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foobar@hosed.example.com', 'hosed', options)
50
+ expect(%i(permerror)).to include(result.code)
51
+ end
52
+
53
+ it 'SPF policies are restricted to 7-bit ascii.' do
54
+ # Checking a possibly different code path for non-ascii chars.
55
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foobar@hosed2.example.com', 'hosed', options)
56
+ expect(%i(permerror)).to include(result.code)
57
+ end
58
+
59
+ it 'SPF policies are restricted to 7-bit ascii.' do
60
+ # Checking yet another code path for non-ascii chars.
61
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foobar@hosed3.example.com', 'hosed', options)
62
+ expect(%i(permerror)).to include(result.code)
63
+ end
64
+
65
+ it 'Non-ascii content in non-SPF related records.' do
66
+ # Non-SPF related TXT records are none of our business.
67
+ result = Coppertone::SpfService.authenticate_email('1.2.3.4', 'foobar@nothosed.example.com', 'hosed', options)
68
+ expect(%i(fail)).to include(result.code)
69
+ expect(result.explanation).to eq('DEFAULT')
70
+ end
71
+
72
+ it 'Mechanisms are separated by spaces only, not any control char.' do
73
+ result = Coppertone::SpfService.authenticate_email('192.0.2.3', 'foobar@ctrl.example.com', 'hosed', options)
74
+ expect(%i(permerror)).to include(result.code)
75
+ end
76
+
77
+ end