spf 0.0.50 → 0.0.51

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5b664a066cda59ad9e2ca7823802da8b0c9116ff
4
- data.tar.gz: c9d87ca329cbe56ebbe98fa883c44be3297056b6
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ODUyYWMzZjg2M2M5NjRkNjVjOWY1MDE2NDQ3ODE4OWJlYmZkZDk0YQ==
5
+ data.tar.gz: !binary |-
6
+ ZmZjZjhkNGU3NTNjNTUwNTdjZDFiOWRlNzEzM2I5Y2QzZTYwZjQ3Nw==
5
7
  SHA512:
6
- metadata.gz: 66f66f59936df7c15e4b6c5d0a59eb9a4df275e661b8b7b0f8b937cfa73d106e4c27afb50e6524c6e4085ca43430b44276ac48e1c4f0f79ba206992a219ce6eb
7
- data.tar.gz: 1ee62b089c30a43da72513f66facee595e212d1616f63ef0d685fd61f061c8c08f129ad7568738add15e4f17851687bee90502cd9ae16831a368e5a40cdbd823
8
+ metadata.gz: !binary |-
9
+ NGI0Yjg3ZTRlNzk0ZjA4MTUzMzEwMjE0YThmMzIyMTQ5ZWUyNTI3NjQwNDEy
10
+ NWE2ODc1ZDA2OTRlNzQ5YjYzZjUzNDY2MmJjMTZhMWNlMmQ5ODdjOWMzYTIx
11
+ N2YzYWFhMjAyZjQyMzBkZmUyMDc0YjUxMzIyM2ZkY2IwYzhhOGI=
12
+ data.tar.gz: !binary |-
13
+ YzBjYTc5ZDA5MzRlMjQ2N2RlNDQ4ZjlhZmU2YzQ1ODYwMWJhMjY4NGU0MDVk
14
+ Yjg0N2RiNDNiMmQ4NTBkNjExN2NjYjRiZTI1Zjk1M2M3NjBkZmMwOTNmNGE1
15
+ Y2M0M2MyNzJhMWNmZThmOTg2ZGNhNTBjMzNmY2M4MWFhNDc1Y2Y=
data/Gemfile.lock CHANGED
@@ -38,7 +38,7 @@ GEM
38
38
  multi_json (~> 1.3)
39
39
  multi_xml (~> 0.5)
40
40
  rack (~> 1.2)
41
- rack (1.6.0)
41
+ rack (1.6.2)
42
42
  rake (10.4.2)
43
43
  rdoc (3.12.2)
44
44
  json (~> 1.4)
data/lib/spf/error.rb CHANGED
@@ -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
@@ -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
 
data/lib/spf/model.rb CHANGED
@@ -139,13 +139,13 @@ class SPF::Term
139
139
  if @parse_text.sub!(/^\/(\d+)/, '')
140
140
  bits = $1.to_i
141
141
  unless bits and bits >= 0 and bits <= 32 and $1 !~ /^0./
142
- error(SPF::TermIPv4PrefixLengthExpected.new(
142
+ error(SPF::TermIPv4PrefixLengthExpectedError.new(
143
143
  "Invalid IPv4 prefix length encountered in '#{@text}'"))
144
144
  return
145
145
  end
146
146
  @ipv4_prefix_length = bits
147
147
  elsif required
148
- error(SPF::TermIPv4PrefixLengthExpected.new(
148
+ error(SPF::TermIPv4PrefixLengthExpectedError.new(
149
149
  "Missing required IPv4 prefix length in '#{@text}"))
150
150
  return
151
151
  else
@@ -168,7 +168,7 @@ class SPF::Term
168
168
  if @parse_text.sub!(/(#{IPV6_ADDRESS_PATTERN})(?=\/|$)/x, '')
169
169
  @ip_address = $1
170
170
  elsif required
171
- error(SPF::TermIPv6AddressExpected.new(
171
+ error(SPF::TermIPv6AddressExpectedError.new(
172
172
  "Missing or invalid required IPv6 address in '#{@text}'"))
173
173
  end
174
174
  @ip_address = @parse_text.dup unless @ip_address
@@ -184,7 +184,7 @@ class SPF::Term
184
184
  end
185
185
  @ipv6_prefix_length = bits
186
186
  elsif required
187
- error(SPF::TermIPv6PrefixLengthExpected.new(
187
+ error(SPF::TermIPv6PrefixLengthExpectedError.new(
188
188
  "Missing required IPv6 prefix length in '#{@text}'"))
189
189
  return
190
190
  else
@@ -214,7 +214,7 @@ class SPF::Term
214
214
 
215
215
  def domain(server, request)
216
216
  if self.instance_variable_defined?(:@domain_spec) and @domain_spec
217
- return @domain_spec
217
+ return SPF::MacroString.new({:server => server, :request => request, :text => @domain_spec.text})
218
218
  end
219
219
  return request.authority_domain
220
220
  end
@@ -446,13 +446,13 @@ class SPF::Mech < SPF::Term
446
446
  server.count_dns_interactive_term(request)
447
447
 
448
448
  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'
449
+ begin
450
+ rrs = server.dns_lookup(domain, 'A')
451
+ return true if rrs.any?
452
+ rescue SPF::DNSNXDomainError => e
453
+ server.count_void_dns_lookup(request)
454
+ return false
453
455
  end
454
-
455
- return false
456
456
  end
457
457
 
458
458
  end
@@ -941,7 +941,7 @@ class SPF::Record
941
941
  if SPF::GlobalMod === mod
942
942
  # Global modifier.
943
943
  if @global_mods[mod_name]
944
- raise SPF::DuplicateGlobalMod.new("Duplicate global modifier '#{mod_name}' encountered")
944
+ raise SPF::DuplicateGlobalModError.new("Duplicate global modifier '#{mod_name}' encountered")
945
945
  end
946
946
  @global_mods[mod_name] = mod
947
947
  elsif SPF::PositionalMod === mod
data/lib/spf/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # encoding: ASCII-8BIT
2
2
  module SPF
3
- VERSION = '0.0.50'
3
+ VERSION = '0.0.51'
4
4
  end
5
5
 
6
6
  # vim:sw=2 sts=2
data/spf.gemspec CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "spf"
9
- s.version = "0.0.50"
9
+ s.version = "0.0.51"
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.50
4
+ version: 0.0.51
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Flury
@@ -16,75 +16,74 @@ dependencies:
16
16
  name: ruby-ip
17
17
  requirement: !ruby/object:Gem::Requirement
18
18
  requirements:
19
- - - "~>"
19
+ - - ~>
20
20
  - !ruby/object:Gem::Version
21
21
  version: 0.9.1
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
- - - "~>"
26
+ - - ~>
27
27
  - !ruby/object:Gem::Version
28
28
  version: 0.9.1
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: rspec
31
31
  requirement: !ruby/object:Gem::Requirement
32
32
  requirements:
33
- - - "~>"
33
+ - - ~>
34
34
  - !ruby/object:Gem::Version
35
35
  version: '2.9'
36
36
  type: :development
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
39
39
  requirements:
40
- - - "~>"
40
+ - - ~>
41
41
  - !ruby/object:Gem::Version
42
42
  version: '2.9'
43
43
  - !ruby/object:Gem::Dependency
44
44
  name: rdoc
45
45
  requirement: !ruby/object:Gem::Requirement
46
46
  requirements:
47
- - - "~>"
47
+ - - ~>
48
48
  - !ruby/object:Gem::Version
49
49
  version: '3'
50
50
  type: :development
51
51
  prerelease: false
52
52
  version_requirements: !ruby/object:Gem::Requirement
53
53
  requirements:
54
- - - "~>"
54
+ - - ~>
55
55
  - !ruby/object:Gem::Version
56
56
  version: '3'
57
57
  - !ruby/object:Gem::Dependency
58
58
  name: bundler
59
59
  requirement: !ruby/object:Gem::Requirement
60
60
  requirements:
61
- - - "~>"
61
+ - - ~>
62
62
  - !ruby/object:Gem::Version
63
63
  version: '1.2'
64
64
  type: :development
65
65
  prerelease: false
66
66
  version_requirements: !ruby/object:Gem::Requirement
67
67
  requirements:
68
- - - "~>"
68
+ - - ~>
69
69
  - !ruby/object:Gem::Version
70
70
  version: '1.2'
71
71
  - !ruby/object:Gem::Dependency
72
72
  name: jeweler
73
73
  requirement: !ruby/object:Gem::Requirement
74
74
  requirements:
75
- - - "~>"
75
+ - - ~>
76
76
  - !ruby/object:Gem::Version
77
77
  version: '1.8'
78
78
  type: :development
79
79
  prerelease: false
80
80
  version_requirements: !ruby/object:Gem::Requirement
81
81
  requirements:
82
- - - "~>"
82
+ - - ~>
83
83
  - !ruby/object:Gem::Version
84
84
  version: '1.8'
85
- description: |2
86
- An object-oriented Ruby implementation of the Sender Policy Framework (SPF)
87
- e-mail sender authentication system, fully compliant with RFC 4408.
85
+ description: ! " An object-oriented Ruby implementation of the Sender Policy Framework
86
+ (SPF)\n e-mail sender authentication system, fully compliant with RFC 4408.\n"
88
87
  email:
89
88
  - code@agari.com
90
89
  - aflury@agari.com
@@ -95,8 +94,8 @@ extensions: []
95
94
  extra_rdoc_files:
96
95
  - README.rdoc
97
96
  files:
98
- - ".document"
99
- - ".rspec"
97
+ - .document
98
+ - .rspec
100
99
  - Gemfile
101
100
  - Gemfile.lock
102
101
  - README.rdoc
@@ -124,17 +123,17 @@ require_paths:
124
123
  - lib
125
124
  required_ruby_version: !ruby/object:Gem::Requirement
126
125
  requirements:
127
- - - ">="
126
+ - - ! '>='
128
127
  - !ruby/object:Gem::Version
129
128
  version: '0'
130
129
  required_rubygems_version: !ruby/object:Gem::Requirement
131
130
  requirements:
132
- - - ">="
131
+ - - ! '>='
133
132
  - !ruby/object:Gem::Version
134
133
  version: '0'
135
134
  requirements: []
136
135
  rubyforge_project:
137
- rubygems_version: 2.4.6
136
+ rubygems_version: 2.6.14
138
137
  signing_key:
139
138
  specification_version: 4
140
139
  summary: Implementation of the Sender Policy Framework