spf 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/spf/eval.rb +3 -1
- data/lib/spf/model.rb +74 -26
- data/lib/spf/version.rb +1 -1
- data/spf.gemspec +1 -1
- metadata +1 -1
data/lib/spf/eval.rb
CHANGED
@@ -69,6 +69,8 @@ class SPF::Server
|
|
69
69
|
@max_void_dns_lookups = options[:max_void_dns_lookups] ||
|
70
70
|
DEFAULT_MAX_VOID_DNS_LOOKUPS
|
71
71
|
|
72
|
+
@raise_exceptions = options.has_key?(:raise_exceptions) ? options[:raise_exceptions] : true
|
73
|
+
|
72
74
|
end
|
73
75
|
|
74
76
|
def result_class(name = nil)
|
@@ -249,7 +251,7 @@ class SPF::Server
|
|
249
251
|
versions.each do |version|
|
250
252
|
klass = RECORD_CLASSES_BY_VERSION[version]
|
251
253
|
begin
|
252
|
-
record = klass.new_from_string(text)
|
254
|
+
record = klass.new_from_string(text, {:raise_exceptions => @raise_exceptions})
|
253
255
|
rescue SPF::InvalidRecordVersionError
|
254
256
|
# Ignore non-SPF and unknown-version records.
|
255
257
|
# Propagate other errors (including syntax errors), though.
|
data/lib/spf/model.rb
CHANGED
@@ -86,12 +86,19 @@ class SPF::Term
|
|
86
86
|
"
|
87
87
|
|
88
88
|
attr_reader :ip_address, :ip_network, :ipv4_prefix_length, :ipv6_prefix_length
|
89
|
+
attr_accessor :errors
|
89
90
|
|
90
|
-
def initialize
|
91
|
+
def initialize(options = {})
|
91
92
|
@ip_address = nil
|
92
93
|
@ip_network = nil
|
93
94
|
@ipv4_prefix_length = nil
|
94
95
|
@ipv6_prefix_length = nil
|
96
|
+
@errors = []
|
97
|
+
end
|
98
|
+
|
99
|
+
def error(exception)
|
100
|
+
@errors << exception
|
101
|
+
raise exception
|
95
102
|
end
|
96
103
|
|
97
104
|
def self.new_from_string(text, options = {})
|
@@ -118,7 +125,7 @@ class SPF::Term
|
|
118
125
|
@ip_address = $1
|
119
126
|
elsif required
|
120
127
|
raise SPF::TermIPv4AddressExpectedError.new(
|
121
|
-
"Missing required IPv4 address in '#{@text}'")
|
128
|
+
"Missing required IPv4 address in '#{@text}'")
|
122
129
|
end
|
123
130
|
end
|
124
131
|
|
@@ -198,6 +205,8 @@ end
|
|
198
205
|
|
199
206
|
class SPF::Mech < SPF::Term
|
200
207
|
|
208
|
+
attr_reader :ip_netblocks, :errors
|
209
|
+
|
201
210
|
DEFAULT_QUALIFIER = SPF::Record::DEFAULT_QUALIFIER
|
202
211
|
def default_ipv4_prefix_length; 32; end
|
203
212
|
def default_ipv6_prefix_length; 128; end
|
@@ -213,7 +222,10 @@ class SPF::Mech < SPF::Term
|
|
213
222
|
}
|
214
223
|
|
215
224
|
def initialize(options)
|
216
|
-
super()
|
225
|
+
super(options)
|
226
|
+
|
227
|
+
@ip_netblocks = []
|
228
|
+
|
217
229
|
@text = options[:text]
|
218
230
|
if not self.instance_variable_defined?(:@parse_text)
|
219
231
|
@parse_text = @text.dup
|
@@ -239,7 +251,7 @@ class SPF::Mech < SPF::Term
|
|
239
251
|
@qualifier = $1 or DEFAULT_QUALIFIER
|
240
252
|
else
|
241
253
|
raise SPF::InvalidMechQualifierError.new(
|
242
|
-
|
254
|
+
"Invalid qualifier encountered in '#{@text}'")
|
243
255
|
end
|
244
256
|
end
|
245
257
|
|
@@ -247,8 +259,7 @@ class SPF::Mech < SPF::Term
|
|
247
259
|
if @parse_text.sub!(/^ (#{NAME_PATTERN}) (?: : (?=.) )? /x, '')
|
248
260
|
@name = $1
|
249
261
|
else
|
250
|
-
raise SPF::InvalidMech.new(
|
251
|
-
"Unexpected mechanism encountered in '#{@text}'")
|
262
|
+
raise SPF::InvalidMech.new("Unexpected mechanism encountered in '#{@text}'")
|
252
263
|
end
|
253
264
|
end
|
254
265
|
|
@@ -302,9 +313,11 @@ class SPF::Mech < SPF::Term
|
|
302
313
|
rrs.each do |rr|
|
303
314
|
if rr.type == 'A'
|
304
315
|
network = IP.new("#{rr.address}/#{ipv4_prefix_length}")
|
316
|
+
@ip_netblocks << network
|
305
317
|
return true if network.contains?(request.ip_address)
|
306
318
|
elsif rr.type == 'AAAA'
|
307
319
|
network = IP.new("#{rr.address}/#{ipv6_prefix_length}")
|
320
|
+
@ip_netblocks << network
|
308
321
|
return true if network.contains?(request.ip_address_v6)
|
309
322
|
elsif rr.type == 'CNAME'
|
310
323
|
# Ignore -- we should have gotten the A/AAAA records anyway.
|
@@ -339,7 +352,7 @@ class SPF::Mech < SPF::Term
|
|
339
352
|
|
340
353
|
class SPF::Mech::A < SPF::Mech
|
341
354
|
|
342
|
-
NAME
|
355
|
+
NAME = 'a'
|
343
356
|
|
344
357
|
def parse_params
|
345
358
|
self.parse_domain_spec
|
@@ -369,7 +382,7 @@ class SPF::Mech < SPF::Term
|
|
369
382
|
|
370
383
|
class SPF::Mech::All < SPF::Mech
|
371
384
|
|
372
|
-
NAME
|
385
|
+
NAME = 'all'
|
373
386
|
|
374
387
|
def parse_params
|
375
388
|
# No parameters.
|
@@ -383,7 +396,7 @@ class SPF::Mech < SPF::Term
|
|
383
396
|
|
384
397
|
class SPF::Mech::Exists < SPF::Mech
|
385
398
|
|
386
|
-
NAME
|
399
|
+
NAME = 'exists'
|
387
400
|
|
388
401
|
def parse_params
|
389
402
|
self.parse_domain_spec(true)
|
@@ -396,12 +409,15 @@ class SPF::Mech < SPF::Term
|
|
396
409
|
def match(server, request, want_result = true)
|
397
410
|
server.count_dns_interactive_term(request)
|
398
411
|
|
412
|
+
# Other method of denoting "potentially ~infinite" netblocks?
|
413
|
+
@ip_netblocks << nil
|
399
414
|
domain = self.domain(server, request)
|
400
415
|
packet = server.dns_lookup(domain, 'A')
|
401
416
|
rrs = (packet.answer or server.count_void_dns_lookup(request))
|
402
417
|
rrs.each do |rr|
|
403
418
|
return true if rr.type == 'A'
|
404
419
|
end
|
420
|
+
|
405
421
|
return false
|
406
422
|
end
|
407
423
|
|
@@ -409,7 +425,7 @@ class SPF::Mech < SPF::Term
|
|
409
425
|
|
410
426
|
class SPF::Mech::IP4 < SPF::Mech
|
411
427
|
|
412
|
-
NAME
|
428
|
+
NAME = 'ip4'
|
413
429
|
|
414
430
|
def parse_params
|
415
431
|
self.parse_ipv4_network(true)
|
@@ -427,6 +443,7 @@ class SPF::Mech < SPF::Term
|
|
427
443
|
ip_network_v6 = IP::V4 === @ip_network ?
|
428
444
|
SPF::Util.ipv4_address_to_ipv6(@ip_network) :
|
429
445
|
@ip_network
|
446
|
+
@ip_netblocks << @ip_network
|
430
447
|
return ip_network_v6.contains?(request.ip_address_v6)
|
431
448
|
end
|
432
449
|
|
@@ -434,7 +451,7 @@ class SPF::Mech < SPF::Term
|
|
434
451
|
|
435
452
|
class SPF::Mech::IP6 < SPF::Mech
|
436
453
|
|
437
|
-
NAME
|
454
|
+
NAME = 'ip6'
|
438
455
|
|
439
456
|
def parse_params
|
440
457
|
self.parse_ipv6_network(true)
|
@@ -448,6 +465,7 @@ class SPF::Mech < SPF::Term
|
|
448
465
|
end
|
449
466
|
|
450
467
|
def match(server, request, want_result = true)
|
468
|
+
@ip_netblocks << @ip_network
|
451
469
|
return @ip_network.contains?(request.ip_address_v6)
|
452
470
|
end
|
453
471
|
|
@@ -455,7 +473,14 @@ class SPF::Mech < SPF::Term
|
|
455
473
|
|
456
474
|
class SPF::Mech::Include < SPF::Mech
|
457
475
|
|
458
|
-
NAME
|
476
|
+
NAME = 'include'
|
477
|
+
|
478
|
+
attr_accessor :nested_record
|
479
|
+
|
480
|
+
def intitialize(options = {})
|
481
|
+
super(options)
|
482
|
+
@nested_record = nil
|
483
|
+
end
|
459
484
|
|
460
485
|
def parse_params
|
461
486
|
self.parse_domain_spec(true)
|
@@ -476,6 +501,8 @@ class SPF::Mech < SPF::Term
|
|
476
501
|
# Process sub-request:
|
477
502
|
result = server.process(sub_request)
|
478
503
|
|
504
|
+
@nested_record = sub_request.record
|
505
|
+
|
479
506
|
# Translate result of sub-request (RFC 4408, 5.9):
|
480
507
|
|
481
508
|
return false unless want_result
|
@@ -498,7 +525,7 @@ class SPF::Mech < SPF::Term
|
|
498
525
|
|
499
526
|
class SPF::Mech::MX < SPF::Mech
|
500
527
|
|
501
|
-
NAME
|
528
|
+
NAME = 'mx'
|
502
529
|
|
503
530
|
def parse_params
|
504
531
|
self.parse_domain_spec
|
@@ -550,7 +577,7 @@ class SPF::Mech < SPF::Term
|
|
550
577
|
end
|
551
578
|
|
552
579
|
class SPF::Mech::PTR < SPF::Mech
|
553
|
-
NAME
|
580
|
+
NAME = 'ptr'
|
554
581
|
|
555
582
|
def parse_params
|
556
583
|
self.parse_domain_spec
|
@@ -678,11 +705,16 @@ class SPF::Mod < SPF::Term
|
|
678
705
|
|
679
706
|
class SPF::Mod::Redirect < SPF::GlobalMod
|
680
707
|
|
681
|
-
attr_reader :domain_spec
|
708
|
+
attr_reader :domain_spec, :included_record
|
682
709
|
|
683
|
-
NAME
|
684
|
-
PRECEDENCE
|
710
|
+
NAME = 'redirect'
|
711
|
+
PRECEDENCE = 0.8
|
685
712
|
|
713
|
+
def initialize(options = {})
|
714
|
+
super(options)
|
715
|
+
@nested_record = nil
|
716
|
+
end
|
717
|
+
|
686
718
|
def parse_params
|
687
719
|
self.parse_domain_spec(true)
|
688
720
|
end
|
@@ -704,6 +736,8 @@ class SPF::Mod < SPF::Term
|
|
704
736
|
# Process sub-request:
|
705
737
|
result = server.process(sub_request)
|
706
738
|
|
739
|
+
@nested_record = sub_request.record
|
740
|
+
|
707
741
|
# Translate result of sub-request (RFC 4408, 6.1/4):
|
708
742
|
if SPF::Result::None === result
|
709
743
|
server.throw_result(:permerror, request,
|
@@ -718,7 +752,7 @@ end
|
|
718
752
|
|
719
753
|
class SPF::Record
|
720
754
|
|
721
|
-
attr_reader :terms, :text
|
755
|
+
attr_reader :terms, :text, :errors, :ip_netblocks
|
722
756
|
|
723
757
|
RESULTS_BY_QUALIFIER = {
|
724
758
|
'' => :pass,
|
@@ -730,9 +764,12 @@ class SPF::Record
|
|
730
764
|
|
731
765
|
def initialize(options)
|
732
766
|
super()
|
733
|
-
@parse_text
|
734
|
-
@terms
|
735
|
-
@global_mods
|
767
|
+
@parse_text = (@text = options[:text] if not self.instance_variable_defined?(:@parse_text)).dup
|
768
|
+
@terms ||= []
|
769
|
+
@global_mods ||= {}
|
770
|
+
@errors = []
|
771
|
+
@ip_netblocks = []
|
772
|
+
@raise_exceptions = options.has_key?(:raise_exceptions) ? options[:raise_exceptions] : true
|
736
773
|
end
|
737
774
|
|
738
775
|
def self.new_from_string(text, options = {})
|
@@ -747,7 +784,18 @@ class SPF::Record
|
|
747
784
|
raise SPF::NothingToParseError.new('Nothing to parse for record')
|
748
785
|
end
|
749
786
|
self.parse_version_tag
|
750
|
-
|
787
|
+
while @parse_text.length > 0
|
788
|
+
term = nil
|
789
|
+
begin
|
790
|
+
term = self.parse_term
|
791
|
+
rescue SPF::Error => e
|
792
|
+
term.errors << e if term
|
793
|
+
@errors << e
|
794
|
+
raise if @raise_exceptions
|
795
|
+
end
|
796
|
+
@ip_netblocks << term.ip_netblocks if term
|
797
|
+
end
|
798
|
+
@ip_netblocks.flatten!
|
751
799
|
#self.parse_end
|
752
800
|
end
|
753
801
|
|
@@ -772,7 +820,7 @@ class SPF::Record
|
|
772
820
|
(?: \x20+ | $ )
|
773
821
|
/x
|
774
822
|
|
775
|
-
|
823
|
+
term = nil
|
776
824
|
if @parse_text.sub!(regex, '') and $&
|
777
825
|
# Looks like a mechanism:
|
778
826
|
mech_text = $1
|
@@ -781,7 +829,7 @@ class SPF::Record
|
|
781
829
|
unless mech_class
|
782
830
|
raise SPF::InvalidMech.new("Unknown mechanism type '#{mech_name}' in '#{@version_tag}' record")
|
783
831
|
end
|
784
|
-
mech = mech_class.new_from_string(mech_text)
|
832
|
+
term = mech = mech_class.new_from_string(mech_text)
|
785
833
|
@terms << mech
|
786
834
|
elsif (
|
787
835
|
@parse_text.sub!(/
|
@@ -799,7 +847,7 @@ class SPF::Record
|
|
799
847
|
mod_class = MOD_CLASSES[mod_name]
|
800
848
|
if mod_class
|
801
849
|
# Known modifier.
|
802
|
-
mod = mod_class.new_from_string(mod_text)
|
850
|
+
term = mod = mod_class.new_from_string(mod_text)
|
803
851
|
if SPF::GlobalMod === mod
|
804
852
|
# Global modifier.
|
805
853
|
unless @global_mods[mod_name]
|
@@ -814,6 +862,7 @@ class SPF::Record
|
|
814
862
|
else
|
815
863
|
raise SPF::JunkInRecordError.new("Junk encountered in record '#{@text}'")
|
816
864
|
end
|
865
|
+
return term
|
817
866
|
end
|
818
867
|
|
819
868
|
def global_mods
|
@@ -834,7 +883,6 @@ class SPF::Record
|
|
834
883
|
begin
|
835
884
|
@terms.each do |term|
|
836
885
|
if SPF::Mech === term
|
837
|
-
#if term.is_a?(SPF::Mech)
|
838
886
|
# Term is a mechanism.
|
839
887
|
mech = term
|
840
888
|
if mech.match(server, request, request.ip_address != nil)
|
data/lib/spf/version.rb
CHANGED
data/spf.gemspec
CHANGED