dns-zone2 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/HISTORY.md +45 -0
- data/README.md +151 -0
- data/Rakefile +15 -0
- data/dns-zone2.gemspec +41 -0
- data/lib/dns/zone.rb +207 -0
- data/lib/dns/zone/rr.rb +87 -0
- data/lib/dns/zone/rr/a.rb +21 -0
- data/lib/dns/zone/rr/aaaa.rb +5 -0
- data/lib/dns/zone/rr/cdnskey.rb +5 -0
- data/lib/dns/zone/rr/cds.rb +5 -0
- data/lib/dns/zone/rr/cname.rb +21 -0
- data/lib/dns/zone/rr/dlv.rb +5 -0
- data/lib/dns/zone/rr/dnskey.rb +38 -0
- data/lib/dns/zone/rr/ds.rb +38 -0
- data/lib/dns/zone/rr/hinfo.rb +31 -0
- data/lib/dns/zone/rr/mx.rb +33 -0
- data/lib/dns/zone/rr/naptr.rb +44 -0
- data/lib/dns/zone/rr/ns.rb +21 -0
- data/lib/dns/zone/rr/nsec.rb +32 -0
- data/lib/dns/zone/rr/nsec3.rb +45 -0
- data/lib/dns/zone/rr/nsec3param.rb +38 -0
- data/lib/dns/zone/rr/ptr.rb +21 -0
- data/lib/dns/zone/rr/record.rb +88 -0
- data/lib/dns/zone/rr/rrsig.rb +54 -0
- data/lib/dns/zone/rr/soa.rb +51 -0
- data/lib/dns/zone/rr/spf.rb +5 -0
- data/lib/dns/zone/rr/srv.rb +38 -0
- data/lib/dns/zone/rr/sshfp.rb +35 -0
- data/lib/dns/zone/rr/txt.rb +24 -0
- data/lib/dns/zone/test_case.rb +27 -0
- data/lib/dns/zone/version.rb +6 -0
- data/test/rr/a_test.rb +37 -0
- data/test/rr/aaaa_test.rb +27 -0
- data/test/rr/cdnskey_test.rb +31 -0
- data/test/rr/cds_test.rb +28 -0
- data/test/rr/cname_test.rb +19 -0
- data/test/rr/dlv_test.rb +28 -0
- data/test/rr/dnskey_test.rb +31 -0
- data/test/rr/ds_test.rb +28 -0
- data/test/rr/hinfo_test.rb +44 -0
- data/test/rr/mx_test.rb +26 -0
- data/test/rr/naptr_test.rb +60 -0
- data/test/rr/ns_test.rb +18 -0
- data/test/rr/nsec3_test.rb +33 -0
- data/test/rr/nsec3param_test.rb +29 -0
- data/test/rr/nsec_test.rb +24 -0
- data/test/rr/ptr_test.rb +19 -0
- data/test/rr/record_test.rb +37 -0
- data/test/rr/rrsig_test.rb +40 -0
- data/test/rr/soa_test.rb +34 -0
- data/test/rr/spf_test.rb +20 -0
- data/test/rr/srv_test.rb +24 -0
- data/test/rr/sshfp_test.rb +24 -0
- data/test/rr/txt_test.rb +44 -0
- data/test/rr_test.rb +50 -0
- data/test/version_test.rb +9 -0
- data/test/zone_test.rb +273 -0
- 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,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
|
data/test/rr/a_test.rb
ADDED
@@ -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
|