dns-zone2 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +2 -0
  3. data/HISTORY.md +45 -0
  4. data/README.md +151 -0
  5. data/Rakefile +15 -0
  6. data/dns-zone2.gemspec +41 -0
  7. data/lib/dns/zone.rb +207 -0
  8. data/lib/dns/zone/rr.rb +87 -0
  9. data/lib/dns/zone/rr/a.rb +21 -0
  10. data/lib/dns/zone/rr/aaaa.rb +5 -0
  11. data/lib/dns/zone/rr/cdnskey.rb +5 -0
  12. data/lib/dns/zone/rr/cds.rb +5 -0
  13. data/lib/dns/zone/rr/cname.rb +21 -0
  14. data/lib/dns/zone/rr/dlv.rb +5 -0
  15. data/lib/dns/zone/rr/dnskey.rb +38 -0
  16. data/lib/dns/zone/rr/ds.rb +38 -0
  17. data/lib/dns/zone/rr/hinfo.rb +31 -0
  18. data/lib/dns/zone/rr/mx.rb +33 -0
  19. data/lib/dns/zone/rr/naptr.rb +44 -0
  20. data/lib/dns/zone/rr/ns.rb +21 -0
  21. data/lib/dns/zone/rr/nsec.rb +32 -0
  22. data/lib/dns/zone/rr/nsec3.rb +45 -0
  23. data/lib/dns/zone/rr/nsec3param.rb +38 -0
  24. data/lib/dns/zone/rr/ptr.rb +21 -0
  25. data/lib/dns/zone/rr/record.rb +88 -0
  26. data/lib/dns/zone/rr/rrsig.rb +54 -0
  27. data/lib/dns/zone/rr/soa.rb +51 -0
  28. data/lib/dns/zone/rr/spf.rb +5 -0
  29. data/lib/dns/zone/rr/srv.rb +38 -0
  30. data/lib/dns/zone/rr/sshfp.rb +35 -0
  31. data/lib/dns/zone/rr/txt.rb +24 -0
  32. data/lib/dns/zone/test_case.rb +27 -0
  33. data/lib/dns/zone/version.rb +6 -0
  34. data/test/rr/a_test.rb +37 -0
  35. data/test/rr/aaaa_test.rb +27 -0
  36. data/test/rr/cdnskey_test.rb +31 -0
  37. data/test/rr/cds_test.rb +28 -0
  38. data/test/rr/cname_test.rb +19 -0
  39. data/test/rr/dlv_test.rb +28 -0
  40. data/test/rr/dnskey_test.rb +31 -0
  41. data/test/rr/ds_test.rb +28 -0
  42. data/test/rr/hinfo_test.rb +44 -0
  43. data/test/rr/mx_test.rb +26 -0
  44. data/test/rr/naptr_test.rb +60 -0
  45. data/test/rr/ns_test.rb +18 -0
  46. data/test/rr/nsec3_test.rb +33 -0
  47. data/test/rr/nsec3param_test.rb +29 -0
  48. data/test/rr/nsec_test.rb +24 -0
  49. data/test/rr/ptr_test.rb +19 -0
  50. data/test/rr/record_test.rb +37 -0
  51. data/test/rr/rrsig_test.rb +40 -0
  52. data/test/rr/soa_test.rb +34 -0
  53. data/test/rr/spf_test.rb +20 -0
  54. data/test/rr/srv_test.rb +24 -0
  55. data/test/rr/sshfp_test.rb +24 -0
  56. data/test/rr/txt_test.rb +44 -0
  57. data/test/rr_test.rb +50 -0
  58. data/test/version_test.rb +9 -0
  59. data/test/zone_test.rb +273 -0
  60. metadata +217 -0
@@ -0,0 +1,38 @@
1
+ # `NSEC3PARAM` resource record.
2
+ #
3
+ # RFC 5155
4
+ class DNS::Zone::RR::NSEC3PARAM < DNS::Zone::RR::Record
5
+
6
+ REGEX_NSEC3PARAM_RDATA = %r{
7
+ (?<algorithm>\d+)\s*
8
+ (?<flags>\d+)\s*
9
+ (?<iterations>\d+)\s*
10
+ (?<salt>\S+)\s*
11
+ }mx
12
+
13
+ attr_accessor :algorithm, :flags, :iterations, :salt
14
+
15
+ def dump
16
+ parts = general_prefix
17
+ parts << algorithm
18
+ parts << flags
19
+ parts << iterations
20
+ parts << salt
21
+ parts.join(' ')
22
+ end
23
+
24
+ def load(string, options = {})
25
+ rdata = load_general_and_get_rdata(string, options)
26
+ return nil unless rdata
27
+
28
+ captures = rdata.match(REGEX_NSEC3PARAM_RDATA)
29
+ return nil unless captures
30
+
31
+ @algorithm = captures[:algorithm].to_i
32
+ @flags = captures[:flags].to_i
33
+ @iterations = captures[:iterations].to_i
34
+ @salt = captures[:salt]
35
+ self
36
+ end
37
+
38
+ end
@@ -0,0 +1,21 @@
1
+ # `PTR` resource record.
2
+ #
3
+ # RFC 1035
4
+ class DNS::Zone::RR::PTR < DNS::Zone::RR::Record
5
+
6
+ attr_accessor :name
7
+
8
+ def dump
9
+ parts = general_prefix
10
+ parts << @name
11
+ parts.join(' ')
12
+ end
13
+
14
+ def load(string, options = {})
15
+ rdata = load_general_and_get_rdata(string, options)
16
+ return nil unless rdata
17
+ @name = rdata
18
+ self
19
+ end
20
+
21
+ end
@@ -0,0 +1,88 @@
1
+ # Parent class of all RR types, common resource record code lives here.
2
+ # Is responsible for building a Ruby object given a RR string.
3
+ #
4
+ # @abstract Each RR TYPE should subclass and override: {#load} and #{dump}
5
+ class DNS::Zone::RR::Record
6
+
7
+ attr_accessor :label, :ttl
8
+ attr_reader :klass
9
+
10
+ def initialize
11
+ @label = '@'
12
+ @klass = 'IN'
13
+ end
14
+
15
+ # FIXME: should we just: `def type; 'SOA'; end` rather then do the class name convension?
16
+ #
17
+ # Figures out TYPE of RR using class name.
18
+ # This means the class name _must_ match the RR ASCII TYPE.
19
+ #
20
+ # When called directly on the parent class (that you should never do), it will
21
+ # return the string as `<type>`, for use with internal tests.
22
+ #
23
+ # @return [String] the RR type
24
+ def type
25
+ name = self.class.name.split('::').last
26
+ return '<type>' if name == 'Record'
27
+ name
28
+ end
29
+
30
+ # Returns 'general' prefix (in parts) that come before the RDATA.
31
+ # Used by all RR types, generates: `[<label>] [<ttl>] [<class>] <type>`
32
+ #
33
+ # @return [Array<String>] rr prefix parts
34
+ def general_prefix
35
+ parts = []
36
+ parts << label
37
+ parts << ttl if ttl
38
+ parts << 'IN'
39
+ parts << type
40
+ parts
41
+ end
42
+
43
+ # Build RR zone file output.
44
+ #
45
+ # @return [String] RR zone file output
46
+ def dump
47
+ general_prefix.join(' ')
48
+ end
49
+
50
+ # @abstract Override to update instance with RR type spesific data.
51
+ # @param string [String] RR ASCII string data
52
+ # @param options [Hash] additional data required to correctly parse a 'whole' zone
53
+ # @option options [String] :last_label The last label used by the previous RR
54
+ # @return [Object]
55
+ def load(string, options = {})
56
+ raise NotImplementedError, "#load method must be implemented by subclass (#{self.class})"
57
+ end
58
+
59
+ # Load 'general' RR data/params and return the remaining RDATA for further parsing.
60
+ #
61
+ # @param string [String] RR ASCII string data
62
+ # @param options [Hash] additional data required to correctly parse a 'whole' zone
63
+ # @return [String] remaining RDATA
64
+ def load_general_and_get_rdata(string, options = {})
65
+ # strip comments, unless its escaped.
66
+ # skip semicolons within "quote segments" (TXT records)
67
+ string.gsub!(/((?<!\\);)(?=(?:[^"]|"[^"]*")*$).*/o, "")
68
+
69
+ captures = string.match(DNS::Zone::RR::REGEX_RR)
70
+ return nil unless captures
71
+
72
+ if [' ', nil].include?(captures[:label])
73
+ @label = options[:last_label]
74
+ else
75
+ @label = captures[:label]
76
+ end
77
+
78
+ # unroll records nested under other origins
79
+ unrolled_origin = options[:last_origin].sub(options[:origin], '').chomp('.') if options[:last_origin]
80
+ if unrolled_origin && !unrolled_origin.empty?
81
+ @label = @label == '@' ? unrolled_origin : "#{@label}.#{unrolled_origin}"
82
+ end
83
+
84
+ @ttl = captures[:ttl]
85
+ captures[:rdata]
86
+ end
87
+
88
+ end
@@ -0,0 +1,54 @@
1
+ # `RRSIG` resource record.
2
+ #
3
+ # RFC 4034
4
+ class DNS::Zone::RR::RRSIG < DNS::Zone::RR::Record
5
+
6
+ REGEX_RRSIG_RDATA = %r{
7
+ (?<type_covered>\S+)\s*
8
+ (?<algorithm>\d+)\s*
9
+ (?<labels>\d+)\s*
10
+ (?<original_ttl>#{DNS::Zone::RR::REGEX_TTL})\s*
11
+ (?<signature_expiration>\d+)\s*
12
+ (?<signature_inception>\d+)\s*
13
+ (?<key_tag>\d+)\s*
14
+ (?<signer>#{DNS::Zone::RR::REGEX_DOMAINNAME})\s*
15
+ (?<signature>#{DNS::Zone::RR::REGEX_CHARACTER_STRING})\s*
16
+ }mx
17
+
18
+ attr_accessor :type_covered, :algorithm, :labels, :original_ttl, :signature_expiration,
19
+ :signature_inception, :key_tag, :signer, :signature
20
+
21
+ def dump
22
+ parts = general_prefix
23
+ parts << type_covered
24
+ parts << algorithm
25
+ parts << labels
26
+ parts << original_ttl
27
+ parts << signature_expiration
28
+ parts << signature_inception
29
+ parts << key_tag
30
+ parts << signer
31
+ parts << signature
32
+ parts.join(' ')
33
+ end
34
+
35
+ def load(string, options = {})
36
+ rdata = load_general_and_get_rdata(string, options)
37
+ return nil unless rdata
38
+
39
+ captures = rdata.match(REGEX_RRSIG_RDATA)
40
+ return nil unless captures
41
+
42
+ @type_covered = captures[:type_covered]
43
+ @algorithm = captures[:algorithm].to_i
44
+ @labels = captures[:labels].to_i
45
+ @original_ttl = captures[:original_ttl].to_i
46
+ @signature_expiration = captures[:signature_expiration].to_i
47
+ @signature_inception = captures[:signature_inception].to_i
48
+ @key_tag = captures[:key_tag].to_i
49
+ @signer = captures[:signer]
50
+ @signature = captures[:signature]
51
+ self
52
+ end
53
+
54
+ end
@@ -0,0 +1,51 @@
1
+ # `SRV` resource record.
2
+ #
3
+ # RFC 1035
4
+ class DNS::Zone::RR::SOA < DNS::Zone::RR::Record
5
+
6
+ REGEX_SOA_RDATA = %r{
7
+ (?<nameserver>#{DNS::Zone::RR::REGEX_DOMAINNAME})\s* # get nameserver domainname
8
+ (?<email>#{DNS::Zone::RR::REGEX_DOMAINNAME})\s* # get mailbox domainname
9
+ (?<serial>\d+)\s*
10
+ (?<refresh_ttl>#{DNS::Zone::RR::REGEX_TTL})\s*
11
+ (?<retry_ttl>#{DNS::Zone::RR::REGEX_TTL})\s*
12
+ (?<expiry_ttl>#{DNS::Zone::RR::REGEX_TTL})\s*
13
+ (?<minimum_ttl>#{DNS::Zone::RR::REGEX_TTL})\s*
14
+ }mx
15
+
16
+ attr_accessor :nameserver, :email, :serial, :refresh_ttl, :retry_ttl, :expiry_ttl, :minimum_ttl
17
+
18
+ def dump
19
+ parts = general_prefix
20
+ parts << nameserver
21
+ parts << email
22
+
23
+ parts << '('
24
+ parts << serial
25
+ parts << refresh_ttl
26
+ parts << retry_ttl
27
+ parts << expiry_ttl
28
+ parts << minimum_ttl
29
+ parts << ')'
30
+ parts.join(' ')
31
+ end
32
+
33
+ def load(string, options = {})
34
+ rdata = load_general_and_get_rdata(string, options)
35
+ return nil unless rdata
36
+
37
+ captures = rdata.match(REGEX_SOA_RDATA)
38
+ return nil unless captures
39
+
40
+ @nameserver = captures[:nameserver]
41
+ @email = captures[:email]
42
+ @serial = captures[:serial].to_i
43
+ @refresh_ttl = captures[:refresh_ttl]
44
+ @retry_ttl = captures[:retry_ttl]
45
+ @expiry_ttl = captures[:expiry_ttl]
46
+ @minimum_ttl = captures[:minimum_ttl]
47
+
48
+ self
49
+ end
50
+
51
+ end
@@ -0,0 +1,5 @@
1
+ # `SPF` resource record.
2
+ #
3
+ # RFC 4408
4
+ class DNS::Zone::RR::SPF < DNS::Zone::RR::TXT
5
+ end
@@ -0,0 +1,38 @@
1
+ # `SRV` resource record.
2
+ #
3
+ # RFC 2782
4
+ class DNS::Zone::RR::SRV < DNS::Zone::RR::Record
5
+
6
+ REGEX_SRV_RDATA = %r{
7
+ (?<priority>\d+)\s*
8
+ (?<weight>\d+)\s*
9
+ (?<port>\d+)\s*
10
+ (?<target>#{DNS::Zone::RR::REGEX_DOMAINNAME})\s*
11
+ }mx
12
+
13
+ attr_accessor :priority, :weight, :port, :target
14
+
15
+ def dump
16
+ parts = general_prefix
17
+ parts << priority
18
+ parts << weight
19
+ parts << port
20
+ parts << target
21
+ parts.join(' ')
22
+ end
23
+
24
+ def load(string, options = {})
25
+ rdata = load_general_and_get_rdata(string, options)
26
+ return nil unless rdata
27
+
28
+ captures = rdata.match(REGEX_SRV_RDATA)
29
+ return nil unless captures
30
+
31
+ @priority = captures[:priority].to_i
32
+ @weight = captures[:weight].to_i
33
+ @port = captures[:port].to_i
34
+ @target = captures[:target]
35
+ self
36
+ end
37
+
38
+ end
@@ -0,0 +1,35 @@
1
+ # `SSHFP` resource record.
2
+ #
3
+ # RFC 4255
4
+ class DNS::Zone::RR::SSHFP < DNS::Zone::RR::Record
5
+
6
+ REGEX_SSHFP_RDATA = %r{
7
+ (?<algorithm_number>\d+)\s*
8
+ (?<fingerprint_type>\d+)\s*
9
+ (?<fingerprint>#{DNS::Zone::RR::REGEX_STRING})\s*
10
+ }mx
11
+
12
+ attr_accessor :algorithm_number, :fingerprint_type, :fingerprint
13
+
14
+ def dump
15
+ parts = general_prefix
16
+ parts << algorithm_number
17
+ parts << fingerprint_type
18
+ parts << fingerprint
19
+ parts.join(' ')
20
+ end
21
+
22
+ def load(string, options = {})
23
+ rdata = load_general_and_get_rdata(string, options)
24
+ return nil unless rdata
25
+
26
+ captures = rdata.match(REGEX_SSHFP_RDATA)
27
+ return nil unless captures
28
+
29
+ @algorithm_number = captures[:algorithm_number].to_i
30
+ @fingerprint_type = captures[:fingerprint_type].to_i
31
+ @fingerprint = captures[:fingerprint]
32
+ self
33
+ end
34
+
35
+ end
@@ -0,0 +1,24 @@
1
+ # `A` resource record.
2
+ #
3
+ # RFC 1035
4
+ class DNS::Zone::RR::TXT < DNS::Zone::RR::Record
5
+
6
+ attr_accessor :text
7
+
8
+ def dump
9
+ parts = general_prefix
10
+ parts << text
11
+ parts.join(' ')
12
+ end
13
+
14
+ def load(string, options = {})
15
+ rdata = load_general_and_get_rdata(string, options)
16
+ return nil unless rdata
17
+
18
+ # extract text from within quotes; allow multiple quoted strings; ignore escaped quotes
19
+ # @text = rdata.scan(/"#{DNS::Zone::RR::REGEX_STRING}"/).flatten.map { |w| %Q{"#{w}"} }.join
20
+ @text = rdata.scan(/"#{DNS::Zone::RR::REGEX_STRING}"/).flat_map { |w| %Q{"#{w&.first}"} }.join(' ')
21
+ self
22
+ end
23
+
24
+ end
@@ -0,0 +1,27 @@
1
+ #
2
+ # test_case.rb - A file used to setup the testing enviroment for the library.
3
+ #
4
+
5
+ require 'rubygems'
6
+
7
+ # --- code coverage on MRI 1.9 ruby only, but disabled by default --------------
8
+ if RUBY_VERSION >= '1.9' && RUBY_ENGINE == 'ruby' && ENV['COVERAGE']
9
+ require 'simplecov'
10
+ #SimpleCov.command_name 'test:unit'
11
+ SimpleCov.start do
12
+ # code coverage groups.
13
+ add_filter 'test/'
14
+ end
15
+ end
16
+
17
+ # --- load our dependencies using bundler --------------------------------------
18
+ require 'bundler/setup'
19
+ require 'minitest/autorun'
20
+ require 'minitest/pride'
21
+
22
+ # --- Load lib to test ---------------------------------------------------------
23
+ require 'dns/zone'
24
+
25
+ # --- Extend DNS::Zone::TestCase -------------------------------------------------
26
+ class DNS::Zone::TestCase < Minitest::Test
27
+ end
@@ -0,0 +1,6 @@
1
+ module DNS
2
+ class Zone
3
+ # Version number (major.minor.tiny)
4
+ Version = '0.3.2'
5
+ end
6
+ end
@@ -0,0 +1,37 @@
1
+ require 'dns/zone/test_case'
2
+
3
+ class RR_A_Test < DNS::Zone::TestCase
4
+
5
+ def test_build_rr__a
6
+ rr = DNS::Zone::RR::A.new
7
+
8
+ # ensure we can set address parameter
9
+ rr.address = '10.0.1.1'
10
+ assert_equal 'A', rr.type
11
+ assert_equal '@ IN A 10.0.1.1', rr.dump
12
+ rr.address = '10.0.2.2'
13
+ assert_equal '@ IN A 10.0.2.2', rr.dump
14
+
15
+ # with a label set
16
+ rr.label = 'labelname'
17
+ assert_equal 'labelname IN A 10.0.2.2', rr.dump
18
+
19
+ # with a ttl
20
+ rr.ttl = '4w'
21
+ assert_equal 'labelname 4w IN A 10.0.2.2', rr.dump
22
+ end
23
+
24
+ def test_load_rr__a
25
+ rr = DNS::Zone::RR::A.new.load('@ IN A 127.0.0.1')
26
+ assert_equal '@', rr.label
27
+ assert_equal 'A', rr.type
28
+ assert_equal '127.0.0.1', rr.address
29
+
30
+ rr = DNS::Zone::RR::A.new.load('www IN A 127.0.0.1')
31
+ assert_equal 'www', rr.label
32
+ assert_equal 'A', rr.type
33
+ assert_equal 'IN', rr.klass
34
+ assert_equal '127.0.0.1', rr.address
35
+ end
36
+
37
+ end
@@ -0,0 +1,27 @@
1
+ require 'dns/zone/test_case'
2
+
3
+ class RR_AAAA_Test < DNS::Zone::TestCase
4
+
5
+ def test_build_rr__aaaa
6
+ rr = DNS::Zone::RR::AAAA.new
7
+
8
+ # ensure we can set address parameter
9
+ rr.address = '2001:db8::3'
10
+ assert_equal 'AAAA', rr.type
11
+ assert_equal '@ IN AAAA 2001:db8::3', rr.dump
12
+ end
13
+
14
+ def test_load_rr__aaaa
15
+ rr = DNS::Zone::RR::AAAA.new.load('@ IN AAAA 2001:db8::6')
16
+ assert_equal '@', rr.label
17
+ assert_equal 'AAAA', rr.type
18
+ assert_equal '2001:db8::6', rr.address
19
+
20
+ rr = DNS::Zone::RR::AAAA.new.load('www IN A 2001:db8::6')
21
+ assert_equal 'www', rr.label
22
+ assert_equal 'AAAA', rr.type
23
+ assert_equal 'IN', rr.klass
24
+ assert_equal '2001:db8::6', rr.address
25
+ end
26
+
27
+ end