dns-zone2 0.3.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.
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