spf 0.0.49 → 0.0.53

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 7d7611765c97ea142a4b2809fc4f75d61ea0ee3d
4
- data.tar.gz: 7b714243e6934102d5e07486227f0db0aeb3c210
2
+ SHA256:
3
+ metadata.gz: 9b5b96521a48a4f0b0a78397d6e21ff99d03b9d144df182bfd68b0369046d73e
4
+ data.tar.gz: 17025632b91737a607a5035b9dfd2405bb080eeaee3e8091cd5e71dc4729cd4d
5
5
  SHA512:
6
- metadata.gz: ca52c3eaec492ee51afde4ad20fac2d78bd48402005f320e700774afe8fb36298ead25da53eb34d04c512d7e5af0661e802b462d1f0c5589606a2eaba5d85117
7
- data.tar.gz: bc10bbbf14fa2bb9a26ac55cde3793a914f5249ff5ee7909347c2dfb9225ce28284d22c064c2cbe2edd54e1b1c2257d665b56a9728b942db6a782b53c8a64188
6
+ metadata.gz: aea5c665821e18e2724d0d0e92ca02a89347eabc53226135944b9de65b7b20d25f64401128606f49367a718e637b2d425fa6bb2fdb0999b3cbecae72c5698852
7
+ data.tar.gz: f4f74a840d9fbe744d5b2fbf52abb5011f5c75672b648e6d0512450d7261997ee4006a83f707ec50300639972a235b0d261e11ac976bddf5a2eea13db8944150
data/Gemfile CHANGED
@@ -9,8 +9,8 @@ gem "ruby-ip", "~> 0.9.1"
9
9
  # Include everything needed to run rake, tests, features, etc.
10
10
  group :development do
11
11
  gem "rspec", "~> 2.9"
12
- gem "rdoc", "~> 3"
12
+ gem "rdoc", "~> 4.3"
13
13
  gem "bundler", "~> 1.2"
14
- gem "jeweler", "~> 1.8"
14
+ gem "jeweler", "~> 2.3", ">= 2.3.9"
15
15
  gem "simplecov", :require => false, :group => :test
16
16
  end
@@ -1,47 +1,55 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- addressable (2.3.7)
5
- builder (3.2.2)
4
+ addressable (2.4.0)
5
+ builder (3.2.4)
6
+ descendants_tracker (0.0.4)
7
+ thread_safe (~> 0.3, >= 0.3.1)
6
8
  diff-lcs (1.2.5)
7
9
  docile (1.1.5)
8
- faraday (0.8.9)
9
- multipart-post (~> 1.2.0)
10
- git (1.2.9.1)
11
- github_api (0.10.1)
12
- addressable
13
- faraday (~> 0.8.1)
14
- hashie (>= 1.2)
15
- multi_json (~> 1.4)
16
- nokogiri (~> 1.5.2)
17
- oauth2
18
- hashie (3.4.0)
19
- highline (1.7.1)
20
- jeweler (1.8.8)
10
+ faraday (0.9.2)
11
+ multipart-post (>= 1.2, < 3)
12
+ git (1.7.0)
13
+ rchardet (~> 1.8)
14
+ github_api (0.16.0)
15
+ addressable (~> 2.4.0)
16
+ descendants_tracker (~> 0.0.4)
17
+ faraday (~> 0.8, < 0.10)
18
+ hashie (>= 3.4)
19
+ mime-types (>= 1.16, < 3.0)
20
+ oauth2 (~> 1.0)
21
+ hashie (4.1.0)
22
+ highline (2.0.3)
23
+ jeweler (2.3.9)
21
24
  builder
22
- bundler (~> 1.0)
25
+ bundler
23
26
  git (>= 1.2.5)
24
- github_api (= 0.10.1)
27
+ github_api (~> 0.16.0)
25
28
  highline (>= 1.6.15)
26
- nokogiri (= 1.5.10)
29
+ nokogiri (>= 1.5.10)
30
+ psych
27
31
  rake
28
32
  rdoc
29
- json (1.8.2)
30
- jwt (1.3.0)
31
- multi_json (1.10.1)
32
- multi_xml (0.5.5)
33
- multipart-post (1.2.0)
34
- nokogiri (1.5.10)
35
- oauth2 (1.0.0)
36
- faraday (>= 0.8, < 0.10)
37
- jwt (~> 1.0)
33
+ semver2
34
+ jwt (2.2.2)
35
+ mime-types (2.99.3)
36
+ mini_portile2 (2.4.0)
37
+ multi_json (1.15.0)
38
+ multi_xml (0.6.0)
39
+ multipart-post (2.1.1)
40
+ nokogiri (1.10.10)
41
+ mini_portile2 (~> 2.4.0)
42
+ oauth2 (1.4.4)
43
+ faraday (>= 0.8, < 2.0)
44
+ jwt (>= 1.0, < 3.0)
38
45
  multi_json (~> 1.3)
39
46
  multi_xml (~> 0.5)
40
- rack (~> 1.2)
41
- rack (1.6.0)
42
- rake (10.4.2)
43
- rdoc (3.12.2)
44
- json (~> 1.4)
47
+ rack (>= 1.2, < 3)
48
+ psych (3.2.0)
49
+ rack (2.2.3)
50
+ rake (13.0.1)
51
+ rchardet (1.8.0)
52
+ rdoc (4.3.0)
45
53
  rspec (2.99.0)
46
54
  rspec-core (~> 2.99.0)
47
55
  rspec-expectations (~> 2.99.0)
@@ -51,19 +59,24 @@ GEM
51
59
  diff-lcs (>= 1.1.3, < 2.0)
52
60
  rspec-mocks (2.99.3)
53
61
  ruby-ip (0.9.3)
62
+ semver2 (3.4.2)
54
63
  simplecov (0.9.2)
55
64
  docile (~> 1.1.0)
56
65
  multi_json (~> 1.0)
57
66
  simplecov-html (~> 0.9.0)
58
67
  simplecov-html (0.9.0)
68
+ thread_safe (0.3.6)
59
69
 
60
70
  PLATFORMS
61
71
  ruby
62
72
 
63
73
  DEPENDENCIES
64
74
  bundler (~> 1.2)
65
- jeweler (~> 1.8)
66
- rdoc (~> 3)
75
+ jeweler (~> 2.3, >= 2.3.9)
76
+ rdoc (~> 4.3)
67
77
  rspec (~> 2.9)
68
78
  ruby-ip (~> 0.9.1)
69
79
  simplecov
80
+
81
+ BUNDLED WITH
82
+ 1.17.3
@@ -50,15 +50,15 @@ module SPF
50
50
  class InvalidModError < SyntaxError; end # Invalid modifier
51
51
  class InvalidTermError < SyntaxError; end # Invalid term
52
52
  class JunkInTermError < SyntaxError; end # Junk encountered in term
53
- class DuplicateGlobalMod < InvalidModError; end # Duplicate global modifier
53
+ class DuplicateGlobalModError < InvalidModError; end # Duplicate global modifier
54
54
  class InvalidMechError < InvalidTermError; end # Invalid mechanism
55
55
  class InvalidMechQualifierError < InvalidMechError; end # Invalid mechanism qualifier
56
56
  class InvalidMechCIDRError < InvalidMechError; end # Invalid CIDR netblock in mech
57
57
  class TermDomainSpecExpectedError < SyntaxError; end # Missing required <domain-spec> in term
58
58
  class TermIPv4AddressExpectedError < SyntaxError; end # Missing required <ip4-network> in term
59
- class TermIPv4PrefixLengthExpected < SyntaxError; end # Missing required <ip4-cidr-length> in term
60
- class TermIPv6AddressExpected < SyntaxError; end # Missing required <ip6-network> in term
61
- class TermIPv6PrefixLengthExpected < SyntaxError; end # Missing required <ip6-cidr-length> in term
59
+ class TermIPv4PrefixLengthExpectedError < SyntaxError; end # Missing required <ip4-cidr-length> in term
60
+ class TermIPv6AddressExpectedError < SyntaxError; end # Missing required <ip6-network> in term
61
+ class TermIPv6PrefixLengthExpectedError < SyntaxError; end # Missing required <ip6-cidr-length> in term
62
62
  class InvalidMacroStringError < SyntaxError; end # Invalid macro string
63
63
  class InvalidMacroError < InvalidMacroStringError
64
64
  end # Invalid macro
@@ -275,7 +275,13 @@ class SPF::Server
275
275
  versions.each do |version|
276
276
  klass = RECORD_CLASSES_BY_VERSION[version]
277
277
  begin
278
- record = klass.new_from_string(text, {:raise_exceptions => @raise_exceptions})
278
+ options = {:raise_exceptions => @raise_exceptions}
279
+ # A MacroString object for domain indicates this is a nested record.
280
+ # Storing the domain.text maintains an association to the include domain.
281
+ if domain.class == SPF::MacroString
282
+ options[:record_domain] = domain.text
283
+ end
284
+ record = klass.new_from_string(text, options)
279
285
  rescue SPF::InvalidRecordVersionError => error
280
286
  if text =~ /#{LOOSE_SPF_MATCH_PATTERN}/
281
287
  possible_matches << text
@@ -1,5 +1,8 @@
1
1
  # encoding: ASCII-8BIT
2
2
  require 'spf/util'
3
+ require 'spf/error'
4
+ require 'uri'
5
+
3
6
 
4
7
  module SPF
5
8
  class MacroString
@@ -22,8 +25,8 @@ module SPF
22
25
  or raise ArgumentError, "Missing required 'text' option"
23
26
  @server = options[:server]
24
27
  @request = options[:request]
28
+ @is_explanation = options[:is_explanation]
25
29
  @expanded = nil
26
- self.expand
27
30
  end
28
31
 
29
32
  attr_reader :text, :server, :request
@@ -43,9 +46,124 @@ module SPF
43
46
  return (@expanded = @text) unless @text =~ /%/
44
47
  # Short-circuit expansion if text has no '%' characters.
45
48
 
49
+ server, request = context ? context : [@server, @request]
50
+
51
+ valid_context(true, server, request)
52
+
46
53
  expanded = ''
47
- # TODO
48
- return (@expanded = @text)
54
+
55
+ text = @text
56
+
57
+ while m = text.match(/ (.*?) %(.) /x) do
58
+ expanded += m[1]
59
+ key = m[2]
60
+
61
+ if (key == '{')
62
+ if m2 = m.post_match.match(/ (\w|_\p{Alpha}+) ([0-9]+)? (r)? ([.\-+,\/_=])? } /x)
63
+ char, rh_parts, reverse, delimiter = m2.captures
64
+
65
+ # Upper-case macro chars trigger URL-escaping AKA percent-encoding
66
+ # (RFC 4408, 8.1/26):
67
+ do_percent_encode = char =~ /\p{Upper}/
68
+ char.downcase!
69
+
70
+ if char == 's' # RFC 4408, 8.1/19
71
+ value = request.identity
72
+ elsif char == 'l' # RFC 4408, 8.1/19
73
+ value = request.localpart
74
+ elsif char == 'o' # RFC 4408, 8.1/19
75
+ value = request.domain
76
+ elsif char == 'd' # RFC 4408, 8.1/6/4
77
+ value = request.authority_domain
78
+ elsif char == 'i' # RFC 4408, 8.1/20, 8.1/21
79
+ ip_address = request.ip_address
80
+ ip_address = SPF::Util.ipv6_address_to_ipv4(ip_address) if SPF::Util.ipv6_address_is_ipv4_mapped(ip_address)
81
+ if IP::V4 === ip_address
82
+ value = ip_address.to_addr
83
+ elsif IP::V6 === ip_address
84
+ value = ip_address.to_hex.upcase.split('').join('.')
85
+ else
86
+ server.throw_result(:permerror, request, "Unexpected IP address version in request")
87
+ end
88
+ elsif char == 'p' # RFC 4408, 8.1/22
89
+ # According to RFC 7208 the "p" macro letter should not be used (or even published).
90
+ # Here it is left unexpanded and transformers and delimiters are not applied.
91
+ value = '%{' + m2.to_s
92
+ rh_parts = nil
93
+ reverse = nil
94
+ elsif char == 'v' # RFC 4408, 8.1/6/7
95
+ if IP::V4 === request.ip_address
96
+ value = 'in-addr'
97
+ elsif IP::V6 === request.ip_address
98
+ value = 'ip6'
99
+ else
100
+ # Unexpected IP address version.
101
+ server.throw_result(:permerror, request, "Unexpected IP address version in request")
102
+ end
103
+ elsif char == 'h' # RFC 4408, 8.1/6/8
104
+ value = request.helo_identity || 'unknown'
105
+ elsif char == 'c' # RFC 4408, 8.1/20, 8.1/21
106
+ raise SPF::InvalidMacroStringError.new("Illegal 'c' macro in non-explanation macro string '#{@text}'") unless @is_explanation
107
+ ip_address = request.ip_address
108
+ value = SPF::Util::ip_address_to_string(ip_address)
109
+ elsif char == 'r' # RFC 4408, 8.1/23
110
+ value = server.hostname || 'unknown'
111
+ elsif char == 't'
112
+ raise SPF::InvalidMacroStringError.new("Illegal 't' macro in non-explanation macro string '#{@text}'") unless @is_explanation
113
+ value = Time.now.to_i.to_s
114
+ elsif char == '_scope'
115
+ # Scope pseudo macro for internal use only!
116
+ value = request.scope.to_s
117
+ else
118
+ # Unknown macro character.
119
+ raise SPF::InvalidMacroStringError.new("Invalid macro character #{char} in macro string '#{@text}'")
120
+ end
121
+
122
+ if rh_parts || reverse
123
+ delimiter ||= self.class.default_split_delimiters
124
+ list = value.split(delimiter)
125
+ list.reverse! if reverse
126
+ # Extract desired parts:
127
+ if rh_parts && rh_parts.to_i > 0
128
+ list = list.last(rh_parts.to_i)
129
+ end
130
+ if rh_parts && rh_parts.to_i == 0
131
+ raise SPF::InvalidMacroStringError.new("Illegal selection of 0 (zero) right-hand parts in macro string '#{@text}'")
132
+ end
133
+ value = list.join(self.class.default_join_delimiter)
134
+ end
135
+
136
+ if do_percent_encode
137
+ unsafe = Regexp.new('^' + self.class.uri_unreserved_chars)
138
+ value = URI.escape(value, unsafe)
139
+ end
140
+
141
+ expanded += value
142
+
143
+ text = m2.post_match
144
+ else
145
+ # Invalid macro expression.
146
+ raise SPF::InvalidMacroStringError.new("Invalid macro expression in macro string '#{@text}'")
147
+ end
148
+ elsif key == '-'
149
+ expanded += '-'
150
+ text = m.post_match
151
+ elsif key == '_'
152
+ expanded += ' '
153
+ text = m.post_match
154
+ elsif key == '%'
155
+ expanded += '%'
156
+ text = m.post_match
157
+ else
158
+ # Invalid macro expression.
159
+ pos = m.offset(2).first
160
+ raise SPF::InvalidMacroStringError.new("Invalid macro expression at pos #{pos} in macro string '#{@text}'")
161
+ end
162
+ end
163
+
164
+ expanded += text # Append remaining unmatched characters.
165
+
166
+ context ? expanded : @expanded = expanded
49
167
  end
50
168
 
51
169
  def to_s
@@ -58,16 +176,15 @@ module SPF
58
176
 
59
177
  def valid_context(required, server = self.server, request = self.request)
60
178
  if not SPF::Server === server
61
- raise MacroExpansionCtxRequired, 'SPF server object required' if required
179
+ raise SPF::MacroExpansionCtxRequiredError.new('SPF server object required') if required
62
180
  return false
63
181
  end
64
182
  if not SPF::Request === request
65
- raise MacroExpansionCtxRequired, 'SPF request object required' if required
183
+ raise SPF::MacroExpansionCtxRequiredError.new('SPF request object required') if required
66
184
  return false
67
185
  end
68
186
  return true
69
187
  end
70
-
71
188
  end
72
189
  end
73
190
 
@@ -86,7 +86,7 @@ class SPF::Term
86
86
  ::
87
87
  "
88
88
 
89
- attr_reader :errors, :ip_netblocks, :ip_address, :ip_network, :ipv4_prefix_length, :ipv6_prefix_length, :domain_spec, :raw_params
89
+ attr_reader :errors, :ip_netblocks, :ip_address, :ip_network, :ipv4_prefix_length, :ipv6_prefix_length, :domain_spec, :raw_params, :record_domain
90
90
 
91
91
  def initialize(options = {})
92
92
  @ip_address = nil
@@ -97,6 +97,7 @@ class SPF::Term
97
97
  @errors = []
98
98
  @ip_netblocks = []
99
99
  @text = options[:text]
100
+ @record_domain = options[:record_domain]
100
101
  @raise_exceptions = options.has_key?(:raise_exceptions) ? options[:raise_exceptions] : true
101
102
  end
102
103
 
@@ -117,6 +118,8 @@ class SPF::Term
117
118
  domain_spec = $1
118
119
  domain_spec.sub!(/^(.*?)\.?$/, $1)
119
120
  @domain_spec = SPF::MacroString.new({:text => domain_spec})
121
+ elsif record_domain
122
+ @domain_spec = SPF::MacroString.new({:text => record_domain})
120
123
  elsif required
121
124
  error(SPF::TermDomainSpecExpectedError.new(
122
125
  "Missing required domain-spec in '#{@text}'"))
@@ -139,13 +142,13 @@ class SPF::Term
139
142
  if @parse_text.sub!(/^\/(\d+)/, '')
140
143
  bits = $1.to_i
141
144
  unless bits and bits >= 0 and bits <= 32 and $1 !~ /^0./
142
- error(SPF::TermIPv4PrefixLengthExpected.new(
145
+ error(SPF::TermIPv4PrefixLengthExpectedError.new(
143
146
  "Invalid IPv4 prefix length encountered in '#{@text}'"))
144
147
  return
145
148
  end
146
149
  @ipv4_prefix_length = bits
147
150
  elsif required
148
- error(SPF::TermIPv4PrefixLengthExpected.new(
151
+ error(SPF::TermIPv4PrefixLengthExpectedError.new(
149
152
  "Missing required IPv4 prefix length in '#{@text}"))
150
153
  return
151
154
  else
@@ -168,7 +171,7 @@ class SPF::Term
168
171
  if @parse_text.sub!(/(#{IPV6_ADDRESS_PATTERN})(?=\/|$)/x, '')
169
172
  @ip_address = $1
170
173
  elsif required
171
- error(SPF::TermIPv6AddressExpected.new(
174
+ error(SPF::TermIPv6AddressExpectedError.new(
172
175
  "Missing or invalid required IPv6 address in '#{@text}'"))
173
176
  end
174
177
  @ip_address = @parse_text.dup unless @ip_address
@@ -184,7 +187,7 @@ class SPF::Term
184
187
  end
185
188
  @ipv6_prefix_length = bits
186
189
  elsif required
187
- error(SPF::TermIPvPrefixLengthExpected.new(
190
+ error(SPF::TermIPv6PrefixLengthExpectedError.new(
188
191
  "Missing required IPv6 prefix length in '#{@text}'"))
189
192
  return
190
193
  else
@@ -214,7 +217,7 @@ class SPF::Term
214
217
 
215
218
  def domain(server, request)
216
219
  if self.instance_variable_defined?(:@domain_spec) and @domain_spec
217
- return @domain_spec
220
+ return SPF::MacroString.new({:server => server, :request => request, :text => @domain_spec.text})
218
221
  end
219
222
  return request.authority_domain
220
223
  end
@@ -446,13 +449,13 @@ class SPF::Mech < SPF::Term
446
449
  server.count_dns_interactive_term(request)
447
450
 
448
451
  domain = self.domain(server, request)
449
- packet = server.dns_lookup(domain, 'A')
450
- rrs = (packet.exchange or server.count_void_dns_lookup(request))
451
- rrs.each do |rr|
452
- return true if rr.type == 'A'
452
+ begin
453
+ rrs = server.dns_lookup(domain, 'A')
454
+ return true if rrs.any?
455
+ rescue SPF::DNSNXDomainError => e
456
+ server.count_void_dns_lookup(request)
457
+ return false
453
458
  end
454
-
455
- return false
456
459
  end
457
460
 
458
461
  end
@@ -844,6 +847,7 @@ class SPF::Record
844
847
  @global_mods ||= {}
845
848
  @errors = []
846
849
  @ip_netblocks = []
850
+ @record_domain = options[:record_domain]
847
851
  @raise_exceptions = options.has_key?(:raise_exceptions) ? options[:raise_exceptions] : true
848
852
  end
849
853
 
@@ -914,7 +918,11 @@ class SPF::Record
914
918
  error(exception)
915
919
  mech_class = SPF::Mech
916
920
  end
917
- term = mech = mech_class.new_from_string(mech_text, {:raise_exceptions => @raise_exceptions})
921
+ options = {:raise_exceptions => @raise_exceptions}
922
+ if instance_variable_defined?("@record_domain")
923
+ options[:record_domain] = @record_domain
924
+ end
925
+ term = mech = mech_class.new_from_string(mech_text, options)
918
926
  term.errors << exception if exception
919
927
  @ip_netblocks << mech.ip_netblocks if mech.ip_netblocks
920
928
  @terms << mech
@@ -941,7 +949,7 @@ class SPF::Record
941
949
  if SPF::GlobalMod === mod
942
950
  # Global modifier.
943
951
  if @global_mods[mod_name]
944
- raise SPF::DuplicateGlobalMod.new("Duplicate global modifier '#{mod_name}' encountered")
952
+ raise SPF::DuplicateGlobalModError.new("Duplicate global modifier '#{mod_name}' encountered")
945
953
  end
946
954
  @global_mods[mod_name] = mod
947
955
  elsif SPF::PositionalMod === mod
@@ -1,6 +1,6 @@
1
1
  # encoding: ASCII-8BIT
2
2
  module SPF
3
- VERSION = '0.0.49'
3
+ VERSION = '0.0.53'
4
4
  end
5
5
 
6
6
  # vim:sw=2 sts=2
@@ -6,7 +6,7 @@
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "spf"
9
- s.version = "0.0.49"
9
+ s.version = "0.0.53"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.49
4
+ version: 0.0.53
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Flury
@@ -133,8 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
133
  - !ruby/object:Gem::Version
134
134
  version: '0'
135
135
  requirements: []
136
- rubyforge_project:
137
- rubygems_version: 2.4.6
136
+ rubygems_version: 3.0.6
138
137
  signing_key:
139
138
  specification_version: 4
140
139
  summary: Implementation of the Sender Policy Framework