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 +13 -5
- data/Gemfile.lock +1 -1
- data/lib/spf/error.rb +4 -4
- data/lib/spf/macro_string.rb +123 -6
- data/lib/spf/model.rb +12 -12
- data/lib/spf/version.rb +1 -1
- data/spf.gemspec +1 -1
- metadata +18 -19
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ODUyYWMzZjg2M2M5NjRkNjVjOWY1MDE2NDQ3ODE4OWJlYmZkZDk0YQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZmZjZjhkNGU3NTNjNTUwNTdjZDFiOWRlNzEzM2I5Y2QzZTYwZjQ3Nw==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
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
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
|
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
|
60
|
-
class
|
61
|
-
class
|
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
|
data/lib/spf/macro_string.rb
CHANGED
@@ -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
|
-
|
48
|
-
|
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
|
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
|
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::
|
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::
|
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::
|
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::
|
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
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
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::
|
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
data/spf.gemspec
CHANGED
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.
|
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:
|
86
|
-
|
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
|
-
-
|
99
|
-
-
|
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.
|
136
|
+
rubygems_version: 2.6.14
|
138
137
|
signing_key:
|
139
138
|
specification_version: 4
|
140
139
|
summary: Implementation of the Sender Policy Framework
|