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 +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
|