spf 0.0.50 → 0.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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