spf 0.0.1 → 0.0.2
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.
- data/lib/spf/eval.rb +7 -6
- data/lib/spf/model.rb +42 -27
- data/lib/spf/request.rb +11 -15
- data/lib/spf/result.rb +2 -2
- data/lib/spf/version.rb +1 -1
- data/spf.gemspec +1 -1
- metadata +1 -1
data/lib/spf/eval.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'ip'
|
2
2
|
require 'resolv'
|
3
3
|
|
4
|
+
require 'spf/error'
|
4
5
|
require 'spf/model'
|
5
6
|
require 'spf/result'
|
6
7
|
|
@@ -46,7 +47,7 @@ class SPF::Server
|
|
46
47
|
def initialize(options = {})
|
47
48
|
@default_authority_explanation = options[:default_authority_explanation] ||
|
48
49
|
DEFAULT_DEFAULT_AUTHORITY_EXPLANATION
|
49
|
-
unless
|
50
|
+
unless SPF::MacroString === @default_authority_explanation
|
50
51
|
@default_authority_explanation = SPF::MacroString.new({
|
51
52
|
:text => @default_authority_explanation,
|
52
53
|
:server => self,
|
@@ -122,7 +123,7 @@ class SPF::Server
|
|
122
123
|
end
|
123
124
|
|
124
125
|
def dns_lookup(domain, rr_type)
|
125
|
-
if
|
126
|
+
if SPF::MacroString === domain
|
126
127
|
domain = domain.expand
|
127
128
|
# Truncate overlong labels at 63 bytes (RFC 4408, 8.1/27)
|
128
129
|
domain.gsub!(/([^.]{63})[^.]+/, "#{$1}")
|
@@ -132,7 +133,7 @@ class SPF::Server
|
|
132
133
|
|
133
134
|
rr_type = self.resource_typeclass_for_rr_type(rr_type)
|
134
135
|
|
135
|
-
domain.sub(
|
136
|
+
domain = domain.sub(/\.$/, '').downcase
|
136
137
|
|
137
138
|
packet = @dns_resolver.getresources(domain, rr_type)
|
138
139
|
|
@@ -214,13 +215,13 @@ class SPF::Server
|
|
214
215
|
|
215
216
|
if records.empty?
|
216
217
|
# RFC 4408, 4.5/7
|
217
|
-
raise SPF::NoAcceptableRecordError('No applicable sender policy available')
|
218
|
+
raise SPF::NoAcceptableRecordError.new('No applicable sender policy available')
|
218
219
|
end
|
219
220
|
|
220
221
|
# Discard all records but the highest acceptable version:
|
221
222
|
preferred_record_class = records[0].class
|
222
223
|
|
223
|
-
records = records.select { |record|
|
224
|
+
records = records.select { |record| preferred_record_class === record }
|
224
225
|
|
225
226
|
if records.length != 1
|
226
227
|
# RFC 4408, 4.5/6
|
@@ -242,7 +243,7 @@ class SPF::Server
|
|
242
243
|
rr_type = resource_typeclass_for_rr_type(rr_type)
|
243
244
|
records = []
|
244
245
|
packet.each do |rr|
|
245
|
-
next unless rr
|
246
|
+
next unless rr_type === rr
|
246
247
|
text = rr.strings.join('')
|
247
248
|
record = false
|
248
249
|
versions.each do |version|
|
data/lib/spf/model.rb
CHANGED
@@ -85,6 +85,15 @@ class SPF::Term
|
|
85
85
|
::
|
86
86
|
"
|
87
87
|
|
88
|
+
attr_reader :ip_address, :ip_network, :ipv4_prefix_length, :ipv6_prefix_length
|
89
|
+
|
90
|
+
def initialize
|
91
|
+
@ip_address = nil
|
92
|
+
@ip_network = nil
|
93
|
+
@ipv4_prefix_length = nil
|
94
|
+
@ipv6_prefix_length = nil
|
95
|
+
end
|
96
|
+
|
88
97
|
def self.new_from_string(text, options = {})
|
89
98
|
#term = SPF::Term.new(options, {:text => text})
|
90
99
|
options[:text] = text
|
@@ -210,7 +219,7 @@ class SPF::Mech < SPF::Term
|
|
210
219
|
@parse_text = @text.dup
|
211
220
|
end
|
212
221
|
if self.instance_variable_defined?(:@domain_spec) and
|
213
|
-
not
|
222
|
+
not SPF::MacroString === @domain_spec
|
214
223
|
@domain_spec = SPF::MacroString.new({:text => @domain_spec})
|
215
224
|
end
|
216
225
|
end
|
@@ -351,7 +360,7 @@ class SPF::Mech < SPF::Term
|
|
351
360
|
return params
|
352
361
|
end
|
353
362
|
|
354
|
-
def match(server, request)
|
363
|
+
def match(server, request, want_result = true)
|
355
364
|
server.count_dns_interactive_term(request)
|
356
365
|
return self.match_in_domain(server, request)
|
357
366
|
end
|
@@ -366,7 +375,7 @@ class SPF::Mech < SPF::Term
|
|
366
375
|
# No parameters.
|
367
376
|
end
|
368
377
|
|
369
|
-
def match(server, request)
|
378
|
+
def match(server, request, want_result = true)
|
370
379
|
return true
|
371
380
|
end
|
372
381
|
|
@@ -384,7 +393,7 @@ class SPF::Mech < SPF::Term
|
|
384
393
|
return @domain_spec ? ':' + @domain_spec : nill
|
385
394
|
end
|
386
395
|
|
387
|
-
def match(server, request)
|
396
|
+
def match(server, request, want_result = true)
|
388
397
|
server.count_dns_interactive_term(request)
|
389
398
|
|
390
399
|
domain = self.domain(server, request)
|
@@ -414,8 +423,8 @@ class SPF::Mech < SPF::Term
|
|
414
423
|
return result
|
415
424
|
end
|
416
425
|
|
417
|
-
def match(server, request)
|
418
|
-
ip_network_v6 =
|
426
|
+
def match(server, request, want_result = true)
|
427
|
+
ip_network_v6 = IP::V4 === @ip_network ?
|
419
428
|
SPF::Util.ipv4_address_to_ipv6(@ip_network) :
|
420
429
|
@ip_network
|
421
430
|
return ip_network_v6.contains?(request.ip_address_v6)
|
@@ -438,7 +447,7 @@ class SPF::Mech < SPF::Term
|
|
438
447
|
return params
|
439
448
|
end
|
440
449
|
|
441
|
-
def match(server, request)
|
450
|
+
def match(server, request, want_result = true)
|
442
451
|
return @ip_network.contains?(request.ip_address_v6)
|
443
452
|
end
|
444
453
|
|
@@ -456,7 +465,8 @@ class SPF::Mech < SPF::Term
|
|
456
465
|
return @domain_spec ? ':' + @domain_spec : nil
|
457
466
|
end
|
458
467
|
|
459
|
-
def match(server, request)
|
468
|
+
def match(server, request, want_result = true)
|
469
|
+
|
460
470
|
server.count_dns_interactive_term(request)
|
461
471
|
|
462
472
|
# Create sub-request with mutated authority domain:
|
@@ -468,17 +478,18 @@ class SPF::Mech < SPF::Term
|
|
468
478
|
|
469
479
|
# Translate result of sub-request (RFC 4408, 5.9):
|
470
480
|
|
471
|
-
return
|
472
|
-
|
481
|
+
return false unless want_result
|
482
|
+
|
483
|
+
return true if SPF::Result::Pass === result
|
473
484
|
|
474
485
|
return false if
|
475
|
-
|
476
|
-
|
477
|
-
|
486
|
+
SPF::Result::Fail === result or
|
487
|
+
SPF::Result::SoftFail === result or
|
488
|
+
SPF::Result::Neutral === result or
|
478
489
|
|
479
490
|
server.throw_result('permerror', request,
|
480
491
|
"Include domain '#{authority_domain}' has no applicable sender policy") if
|
481
|
-
|
492
|
+
SPF::Result::None === result
|
482
493
|
|
483
494
|
# Propagate any other results (including {Perm,Temp}Error) as-is:
|
484
495
|
raise result
|
@@ -508,7 +519,7 @@ class SPF::Mech < SPF::Term
|
|
508
519
|
return params
|
509
520
|
end
|
510
521
|
|
511
|
-
def match(server, request)
|
522
|
+
def match(server, request, want_result = true)
|
512
523
|
|
513
524
|
server.count_dns_interactive_term(request)
|
514
525
|
|
@@ -549,7 +560,7 @@ class SPF::Mech < SPF::Term
|
|
549
560
|
return @domain_spec ? ':' + @domain_spec : nil
|
550
561
|
end
|
551
562
|
|
552
|
-
def match(server, request)
|
563
|
+
def match(server, request, want_result = true)
|
553
564
|
return SPF::Util.valid_domain_for_ip_address(
|
554
565
|
server, request, request.ip_address, self.domain(server, request)) ?
|
555
566
|
true : false
|
@@ -566,7 +577,7 @@ class SPF::Mod < SPF::Term
|
|
566
577
|
|
567
578
|
@parse_text = @text.dup unless @parse_text
|
568
579
|
|
569
|
-
if @domain_spec and not
|
580
|
+
if @domain_spec and not SPF::MacroString === @domain_spec
|
570
581
|
@domain_spec = SPF::MacroString.new({:text => @domain_spec})
|
571
582
|
end
|
572
583
|
end
|
@@ -684,7 +695,7 @@ class SPF::Mod < SPF::Term
|
|
684
695
|
server.count_dns_interactive_term(request)
|
685
696
|
|
686
697
|
# Only perform redirection if no mechanism matched (RFC 4408, 6.1/1):
|
687
|
-
return unless
|
698
|
+
return unless SPF::Result::NeutralByDefault === result
|
688
699
|
|
689
700
|
# Create sub-request with mutated authorithy domain:
|
690
701
|
authority_domain = @domain_spec.new({:server => server, :request => request})
|
@@ -694,7 +705,7 @@ class SPF::Mod < SPF::Term
|
|
694
705
|
result = server.process(sub_request)
|
695
706
|
|
696
707
|
# Translate result of sub-request (RFC 4408, 6.1/4):
|
697
|
-
if
|
708
|
+
if SPF::Result::None === result
|
698
709
|
server.throw_result(:permerror, request,
|
699
710
|
"Redirect domain '#{authority_domain}' has no applicable sender policy")
|
700
711
|
end
|
@@ -789,13 +800,13 @@ class SPF::Record
|
|
789
800
|
if mod_class
|
790
801
|
# Known modifier.
|
791
802
|
mod = mod_class.new_from_string(mod_text)
|
792
|
-
if
|
803
|
+
if SPF::GlobalMod === mod
|
793
804
|
# Global modifier.
|
794
805
|
unless @global_mods[mod_name]
|
795
806
|
raise SPF::DuplicateGlobalMod.new("Duplicate global modifier '#{mod_name}' encountered")
|
796
807
|
end
|
797
808
|
@global_mods[mod_name] = mod
|
798
|
-
elsif
|
809
|
+
elsif SPF::PositionalMod === mod
|
799
810
|
# Positional modifier, queue normally:
|
800
811
|
@terms << mod
|
801
812
|
end
|
@@ -822,26 +833,26 @@ class SPF::Record
|
|
822
833
|
raise SPF::OptionRequiredError.new('Request object required for record evaluation') unless request
|
823
834
|
begin
|
824
835
|
@terms.each do |term|
|
825
|
-
if
|
836
|
+
if SPF::Mech === term
|
837
|
+
#if term.is_a?(SPF::Mech)
|
826
838
|
# Term is a mechanism.
|
827
839
|
mech = term
|
828
|
-
if mech.match(server, request)
|
840
|
+
if mech.match(server, request, request.ip_address != nil)
|
829
841
|
result_name = RESULTS_BY_QUALIFIER[mech.qualifier]
|
830
842
|
result_class = server.result_class(result_name)
|
831
843
|
result = result_class.new([server, request, "Mechanism '#{term}' matched"])
|
832
844
|
mech.explain(server, request, result)
|
833
845
|
raise result
|
834
846
|
end
|
835
|
-
elsif
|
847
|
+
elsif SPF::PositionalMod === term
|
836
848
|
# Term is a positional modifier.
|
837
849
|
mod = term
|
838
850
|
mod.process(server, request)
|
839
|
-
elsif
|
851
|
+
elsif SPF::UnknownMod === term
|
840
852
|
# Term is an unknown modifier. Ignore it (RFC 4408, 6/3).
|
841
853
|
else
|
842
854
|
# Invalid term object encountered:
|
843
|
-
raise SPF::UnexpectedTermObjectError.new(
|
844
|
-
"Unexpected term object '#{term}' encountered.")
|
855
|
+
raise SPF::UnexpectedTermObjectError.new("Unexpected term object '#{term}' encountered.")
|
845
856
|
end
|
846
857
|
end
|
847
858
|
rescue SPF::Result => result
|
@@ -880,6 +891,10 @@ class SPF::Record
|
|
880
891
|
'v=spf1'
|
881
892
|
end
|
882
893
|
|
894
|
+
def self.version_tag
|
895
|
+
'v=spf1'
|
896
|
+
end
|
897
|
+
|
883
898
|
def version_tag_pattern
|
884
899
|
" v=spf(1) (?= \\x20+ | $ ) "
|
885
900
|
end
|
data/lib/spf/request.rb
CHANGED
@@ -25,7 +25,7 @@ class SPF::Request
|
|
25
25
|
@state = {}
|
26
26
|
@versions = options[:versions]
|
27
27
|
@scope = options[:scope] || :mfrom
|
28
|
-
@scope = @scope.to_sym if @scope
|
28
|
+
@scope = @scope.to_sym if String === @scope
|
29
29
|
@_authority_domain = options[:authority_domain]
|
30
30
|
@identity = options[:identity]
|
31
31
|
@ip_address = options[:ip_address]
|
@@ -40,14 +40,13 @@ class SPF::Request
|
|
40
40
|
raise SPF::InvalidScopeError.new("Invalid scope '#{@scope}'")
|
41
41
|
|
42
42
|
# Versions:
|
43
|
-
if
|
44
|
-
if @versions
|
43
|
+
if @versions
|
44
|
+
if Symbol === @versions
|
45
45
|
# Single version specified as a symbol:
|
46
46
|
@versions = [@versions]
|
47
|
-
elsif not @versions
|
47
|
+
elsif not Array === @versions
|
48
48
|
# Something other than symbol or array specified:
|
49
|
-
raise SPF::InvalidOptionValueError.new(
|
50
|
-
"'versions' option must be symbol or array")
|
49
|
+
raise SPF::InvalidOptionValueError.new("'versions' option must be symbol or array")
|
51
50
|
end
|
52
51
|
|
53
52
|
# All requested record versions must be supported:
|
@@ -91,24 +90,21 @@ class SPF::Request
|
|
91
90
|
@helo_identity ||= @identity
|
92
91
|
end
|
93
92
|
|
94
|
-
|
95
|
-
if [:helo, :mfrom, :pra].find(@scope) and not self.instance_variable_defined?(:@ip_address)
|
96
|
-
raise SPF::OptionRequiredError.new("Missing required 'ip_address' option")
|
97
|
-
end
|
93
|
+
return unless @ip_address
|
98
94
|
|
99
95
|
# Ensure ip_address is an IP object:
|
100
|
-
unless @ip_address
|
96
|
+
unless IP === @ip_address
|
101
97
|
@ip_address = IP.new(@ip_address)
|
102
98
|
end
|
103
99
|
|
104
100
|
# Convert IPv4 address to IPv4-mapped IPv6 address:
|
105
101
|
|
106
|
-
if SPF::Util.ipv6_address_is_ipv4_mapped(
|
102
|
+
if SPF::Util.ipv6_address_is_ipv4_mapped(@ip_address)
|
107
103
|
@ip_address_v6 = @ip_address # Accept as IPv6 address as-is
|
108
104
|
@ip_address = SPF::Util.ipv6_address_to_ipv4(@ip_address)
|
109
|
-
elsif
|
105
|
+
elsif IP::V4 === @ip_address
|
110
106
|
@ip_address_v6 = SPF::Util.ipv4_address_to_ipv6(@ip_address)
|
111
|
-
elsif
|
107
|
+
elsif IP::V6 === @ip_address
|
112
108
|
@ip_address_v6 = @ip_address
|
113
109
|
else
|
114
110
|
raise SPF::InvalidOptionValueError.new("Unexpected IP address version");
|
@@ -131,7 +127,7 @@ class SPF::Request
|
|
131
127
|
unless field
|
132
128
|
raise SPF::OptionRequiredError.new('Field name required')
|
133
129
|
end
|
134
|
-
if value and
|
130
|
+
if value and Fixnum === value
|
135
131
|
@state[field] = 0 unless @state[field]
|
136
132
|
@state[field] += value
|
137
133
|
else
|
data/lib/spf/result.rb
CHANGED
@@ -140,7 +140,7 @@ class SPF::Result < Exception
|
|
140
140
|
|
141
141
|
def klass(name=nil)
|
142
142
|
if name
|
143
|
-
name = name.to_sym if name
|
143
|
+
name = name.to_sym if String === name
|
144
144
|
return self.RESULT_CLASSES[name]
|
145
145
|
else
|
146
146
|
return name
|
@@ -150,7 +150,7 @@ class SPF::Result < Exception
|
|
150
150
|
def isa_by_name(name)
|
151
151
|
suspect_class = self.klass(name)
|
152
152
|
return false unless suspect_class
|
153
|
-
return self
|
153
|
+
return suspect_class === self
|
154
154
|
end
|
155
155
|
|
156
156
|
def is_code(code)
|
data/lib/spf/version.rb
CHANGED
data/spf.gemspec
CHANGED