spf 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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